{
  "openapi": "3.1.0",
  "info": {
    "title": "rrule.net API",
    "description": "Context-aware, explainable scheduling API built on the @rrulenet model. Define schedules from natural language, cron, RRule, or canonical recurrence JSON; preview occurrences; attach explicit event and target context; and let rrule.net execute reliably across timezones for humans and AI agents.\n\n**Try it now:** Public endpoints like `/v1/schedules/validate` and `/v1/schedules/simulate` require no authentication. Start experimenting immediately!\n\n**Need persistence?** Endpoints that create or manage schedules require authentication. Use **API Keys** (recommended) for static authentication, or **OAuth tokens** for programmatic access. Create API keys in your [dashboard](https://rrule.net/dashboard).",
    "version": "1.0.0",
    "contact": {
      "name": "rrule.net",
      "url": "https://rrule.net"
    }
  },
  "servers": [
    {
      "url": "https://api.rrule.net",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Public",
      "description": "Public endpoints — no authentication required"
    },
    {
      "name": "Schedules",
      "description": "Schedule CRUD and lifecycle management — authentication required"
    },
    {
      "name": "API Keys",
      "description": "API key management (recommended auth method) — authentication required"
    },
    {
      "name": "OAuth",
      "description": "OAuth2 client credentials flow and client management (alternative auth method) — authentication required"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": ["Public"],
        "summary": "Health check",
        "description": "Returns the current status of the API service.",
        "operationId": "healthCheck",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is healthy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "ok"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/schedules/validate": {
      "post": {
        "tags": ["Public"],
        "summary": "Validate and normalize a scheduling rule",
        "description": "Validates one schedule input and returns canonical JSON with explanation. Prefer the `input` string for public calls (RRule, cron, or natural language). For natural language, `timezone` is a fallback: one timezone or location explicitly stated in `input` takes precedence and is returned as the effective timezone; several execution contexts may produce `targets`. Use `recurrence` when you already have canonical @rrulenet/recurrence JSON. Use `event_schedule` only when you already have explicit @rrulenet/events JSON for contextual scheduling. Provide exactly one of `input`, `recurrence`, or `event_schedule`.",
        "operationId": "validateSchedule",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ValidateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/v1/schedules/simulate": {
      "post": {
        "tags": ["Public"],
        "summary": "Simulate future occurrences",
        "description": "Generates a list of future occurrences for one schedule input. Use `input` for RRule, cron, or natural language. For natural language, an explicitly stated timezone or location takes precedence over the fallback `timezone`. Use `recurrence` for canonical recurrence JSON; use `event_schedule` only when you already have explicit @rrulenet/events JSON for contextual scheduling. Provide exactly one executable source. All returned timestamps are in UTC (ISO 8601 with Z suffix).",
        "operationId": "simulateSchedule",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SimulateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Simulated occurrences",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SimulateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid recurrence, RRule, or parameters",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/v1/schedules/parse": {
      "post": {
        "tags": ["Public"],
        "summary": "Parse natural language into recurrence JSON",
        "description": "Deprecated compatibility endpoint. Use `POST /v1/schedules/validate` with natural language input instead.",
        "operationId": "parseNaturalLanguage",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ParseRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Parsed recurrence",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/v1/schedules": {
      "get": {
        "tags": ["Schedules"],
        "summary": "List schedules",
        "description": "Returns a paginated list of the authenticated user's schedules.\n\n**Authentication:** Use an API Key (recommended) or OAuth token. Create an API key in your [dashboard](https://rrule.net/dashboard) (Settings → API Keys), or obtain an OAuth token via `POST /oauth/token`.",
        "operationId": "listSchedules",
        "security": [{ "apiKey": [] }, { "bearerAuth": [] }],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 100 },
            "description": "Maximum number of schedules to return"
          },
          {
            "name": "offset",
            "in": "query",
            "schema": { "type": "integer", "minimum": 0, "default": 0 },
            "description": "Number of schedules to skip"
          },
          {
            "name": "status",
            "in": "query",
            "schema": { "type": "string", "enum": ["active", "paused"] },
            "description": "Filter by schedule status"
          }
        ],
        "responses": {
          "200": {
            "description": "List of schedules",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schedules": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Schedule" }
                    },
                    "count": { "type": "integer", "description": "Number of schedules in this response" },
                    "total": { "type": "integer", "description": "Total number of schedules matching the query" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Schedule cannot be resumed because it has no future runs",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": {
                  "error": "Cannot resume schedule",
                  "code": "schedule_has_no_future_occurrences",
                  "message": "No future runs remain",
                  "details": "Schedule has no future occurrences"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      },
      "post": {
        "tags": ["Schedules"],
        "summary": "Create a schedule",
        "description": "Creates a persistent schedule with a webhook. Prefer the `input` string for public API calls (RRule, cron, or natural language). For natural language, `timezone` is a fallback: one timezone or location explicitly stated in `input` takes precedence and is persisted as the effective schedule timezone; several execution contexts may produce `targets`. Use `recurrence` only when you already have canonical @rrulenet/recurrence JSON. Use `event_schedule` only when you already have explicit @rrulenet/events JSON for contextual scheduling. Use `targets` when one logical schedule should execute in several target contexts. Each target can be backed by `input`, `recurrence`, or explicit @rrulenet/events `event_schedule` JSON. When `targets` is provided, top-level `input` is allowed only as the original user-facing description for audit and UX; executable sources must live on each target. Provide exactly one executable shape: `input`, `recurrence`, `event_schedule`, or `targets`. Do not combine `targets` with top-level `recurrence` or `event_schedule`. The schedule is validated before creation.\n\n**Authentication:** Use an API Key (recommended) or OAuth token.",
        "operationId": "createSchedule",
        "security": [{ "apiKey": [] }, { "bearerAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateScheduleRequest"
              },
              "examples": {
                "input": {
                  "summary": "Create from input string",
                  "value": {
                    "name": "Weekly operations sync",
                    "input": "Every Monday at 9am",
                    "timezone": "Europe/Paris",
                    "webhook": { "url": "https://example.com/webhooks/rrule" }
                  }
                },
                "targets": {
                  "summary": "Create from explicit targets",
                  "value": {
                    "name": "9am local market sends",
                    "input": "Send every day at 9am local time in Paris and Perth",
                    "timezone": "UTC",
                    "targets": [
                      {
                        "id": "paris",
                        "label": "Paris audience",
                        "input": "Every day at 9am",
                        "timezone": "Europe/Paris",
                        "metadata": { "market": "fr", "segmentId": "brevo-fr" }
                      },
                      {
                        "id": "perth",
                        "label": "Perth audience",
                        "input": "Every day at 9am",
                        "timezone": "Australia/Perth",
                        "metadata": { "market": "au", "segmentId": "brevo-au" }
                      }
                    ],
                    "webhook": { "url": "https://example.com/webhooks/rrule" }
                  }
                },
                "eventTargets": {
                  "summary": "Create target-aware maintenance triggers",
                  "description": "Contextual scheduling shape for explicit @rrulenet/events JSON. Event-relative trigger schedules do not include recurrence.",
                  "value": {
                    "name": "Maintenance customer triggers",
                    "timezone": "UTC",
                    "targets": [
                      {
                        "id": "paris",
                        "label": "Paris customers",
                        "timezone": "Europe/Paris",
                        "event_schedule": {
                          "kind": "event-schedule",
                          "timezone": "Europe/Paris",
                          "events": {
                            "kind": "event-set",
                            "id": "inline.maintenance.2026-09-15",
                            "version": "user-snapshot-1",
                            "source": "inline",
                            "timezone": "UTC",
                            "metadata": { "name": "September maintenance window" },
                            "members": [
                              {
                                "kind": "interval",
                                "id": "maintenance-window",
                                "label": "September maintenance window",
                                "start": "2026-09-15T02:00:00Z",
                                "end": "2026-09-15T04:00:00Z"
                              }
                            ]
                          },
                          "transform": {
                            "kind": "event-relative",
                            "triggers": [
                              { "before": { "days": 7 }, "time": "09:00" },
                              { "before": { "days": 1 }, "time": "09:00" }
                            ]
                          }
                        },
                        "metadata": { "market": "fr", "segmentId": "brevo-fr" }
                      },
                      {
                        "id": "perth",
                        "label": "Perth customers",
                        "timezone": "Australia/Perth",
                        "event_schedule": {
                          "kind": "event-schedule",
                          "timezone": "Australia/Perth",
                          "events": {
                            "kind": "event-set",
                            "id": "inline.maintenance.2026-09-15",
                            "version": "user-snapshot-1",
                            "source": "inline",
                            "timezone": "UTC",
                            "metadata": { "name": "September maintenance window" },
                            "members": [
                              {
                                "kind": "interval",
                                "id": "maintenance-window",
                                "label": "September maintenance window",
                                "start": "2026-09-15T02:00:00Z",
                                "end": "2026-09-15T04:00:00Z"
                              }
                            ]
                          },
                          "transform": {
                            "kind": "event-relative",
                            "triggers": [
                              { "before": { "days": 7 }, "time": "09:00" },
                              { "before": { "days": 1 }, "time": "09:00" }
                            ]
                          }
                        },
                        "metadata": { "market": "au", "segmentId": "brevo-au" }
                      }
                    ],
                    "webhook": { "url": "https://example.com/webhooks/rrule" }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Schedule created",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Schedule" }
              }
            }
          },
          "400": {
            "description": "Invalid input or rule produces no future occurrences",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/v1/schedules/{id}": {
      "get": {
        "tags": ["Schedules"],
        "summary": "Get a schedule",
        "description": "Returns the full details of a single schedule by its ID.\n\n**Authentication:** Use an API Key (recommended) or OAuth token.",
        "operationId": "getSchedule",
        "security": [{ "apiKey": [] }, { "bearerAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/ScheduleId" }
        ],
        "responses": {
          "200": {
            "description": "Schedule details",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Schedule" }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Schedule not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "409": {
            "description": "Schedule id prefix is ambiguous within the caller's schedules",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      },
      "patch": {
        "tags": ["Schedules"],
        "summary": "Update a schedule",
        "description": "Updates the webhook configuration of a schedule. The recurrence, legacy RRule, input, and timezone fields are immutable.",
        "operationId": "updateSchedule",
        "security": [{ "apiKey": [] }, { "bearerAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/ScheduleId" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateScheduleRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated schedule",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Schedule" }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Schedule not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "409": {
            "description": "Schedule id prefix is ambiguous within the caller's schedules",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      },
      "delete": {
        "tags": ["Schedules"],
        "summary": "Delete a schedule",
        "description": "Permanently deletes a schedule and its associated data.",
        "operationId": "deleteSchedule",
        "security": [{ "apiKey": [] }, { "bearerAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/ScheduleId" }
        ],
        "responses": {
          "204": {
            "description": "Schedule deleted",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            }
          },
          "401": {
            "description": "Missing or invalid token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Schedule not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "409": {
            "description": "Schedule id prefix is ambiguous within the caller's schedules",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/v1/schedules/{id}/targets/{targetId}": {
      "patch": {
        "tags": ["Schedules"],
        "summary": "Update schedule target context",
        "description": "Updates mutable target routing context. Target label and metadata are mutable; target id, timezone, recurrence, and event schedule sources are immutable.",
        "operationId": "updateScheduleTarget",
        "security": [{ "apiKey": [] }, { "bearerAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/ScheduleId" },
          { "$ref": "#/components/parameters/TargetId" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateScheduleTargetRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated schedule",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Schedule" }
              }
            }
          },
          "400": {
            "description": "Invalid target update payload",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Schedule or target not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "409": {
            "description": "Schedule id prefix is ambiguous within the caller's schedules",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/v1/schedules/{id}/pause": {
      "post": {
        "tags": ["Schedules"],
        "summary": "Pause a schedule",
        "description": "Pauses a schedule so it will no longer trigger webhooks. This operation is idempotent.",
        "operationId": "pauseSchedule",
        "security": [{ "apiKey": [] }, { "bearerAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/ScheduleId" }
        ],
        "responses": {
          "200": {
            "description": "Paused schedule",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Schedule" }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Schedule not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "409": {
            "description": "Schedule id prefix is ambiguous within the caller's schedules",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/v1/schedules/{id}/resume": {
      "post": {
        "tags": ["Schedules"],
        "summary": "Resume a schedule",
        "description": "Resumes a paused schedule, re-enabling webhook execution. This operation is idempotent.",
        "operationId": "resumeSchedule",
        "security": [{ "apiKey": [] }, { "bearerAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/ScheduleId" }
        ],
        "responses": {
          "200": {
            "description": "Resumed schedule",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Schedule" }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Schedule not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "409": {
            "description": "Schedule id prefix is ambiguous within the caller's schedules",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/v1/schedules/{id}/executions": {
      "get": {
        "tags": ["Schedules"],
        "summary": "Get execution history",
        "description": "Returns the execution history for a given schedule, ordered by most recent first.",
        "operationId": "getExecutions",
        "security": [{ "apiKey": [] }, { "bearerAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/ScheduleId" },
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 100 },
            "description": "Maximum number of executions to return"
          },
          {
            "name": "offset",
            "in": "query",
            "schema": { "type": "integer", "minimum": 0, "default": 0 },
            "description": "Number of executions to skip"
          }
        ],
        "responses": {
          "200": {
            "description": "Execution history",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "executions": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/ScheduleExecution" }
                    },
                    "count": { "type": "integer" }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Schedule not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "409": {
            "description": "Schedule id prefix is ambiguous within the caller's schedules",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "tags": ["OAuth"],
        "summary": "Obtain an access token",
        "description": "Exchange client credentials for a resource-bound, scoped Bearer token using the OAuth 2.0 Client Credentials flow. Use `application/x-www-form-urlencoded` for standards-compatible clients; JSON remains accepted for backward compatibility.\n\n**How to get credentials:**\n1. Sign in to your [dashboard](https://rrule.net/dashboard)\n2. Go to Tokens → New Credential\n3. Save your `client_id` and `client_secret` (shown only once)\n4. Request a token for `https://api.rrule.net` or `https://mcp.rrule.net`\n5. Send the token only to the resource named in its `aud` claim\n\n**Token lifetime:** 1 hour. Request a new token when it expires.",
        "operationId": "oauthToken",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": { "$ref": "#/components/schemas/OAuthTokenRequest" }
            },
            "application/json": {
              "schema": { "$ref": "#/components/schemas/OAuthTokenRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Access token issued",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "access_token": { "type": "string", "description": "JWT Bearer token" },
                    "expires_in": { "type": "integer", "description": "Token lifetime in seconds", "example": 3600 },
                    "token_type": { "type": "string", "enum": ["bearer"], "example": "bearer" },
                    "scope": { "type": "string", "example": "schedules:read schedules:write" }
                  },
                  "required": ["access_token", "expires_in", "token_type", "scope"]
                }
              }
            }
          },
          "401": {
            "description": "Invalid client credentials",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/v1/oauth/clients": {
      "get": {
        "tags": ["OAuth"],
        "summary": "List OAuth clients",
        "description": "Returns all OAuth2 clients for the authenticated user. The client_secret is never included in list responses.\n\n**Authentication:** Use an API Key (recommended) or OAuth token.",
        "operationId": "listOAuthClients",
        "security": [{ "apiKey": [] }, { "bearerAuth": [] }],
        "responses": {
          "200": {
            "description": "List of OAuth clients",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/OAuthClientPublic" }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      },
      "post": {
        "tags": ["OAuth"],
        "summary": "Create an OAuth client",
        "description": "Creates a new OAuth2 client. The client_secret is returned only once in the response — it cannot be retrieved later. Store it securely.\n\n**Authentication:** Use an API Key (recommended) or OAuth token.",
        "operationId": "createOAuthClient",
        "security": [{ "apiKey": [] }, { "bearerAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["name"],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Human-readable name for the client",
                    "example": "My Mobile App"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OAuth client created — includes the one-time client_secret",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/OAuthClientCreated" }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/v1/oauth/clients/{id}": {
      "delete": {
        "tags": ["OAuth"],
        "summary": "Revoke an OAuth client",
        "description": "Soft-revokes an OAuth2 client. Revoked clients can no longer obtain access tokens. This action is irreversible.",
        "operationId": "revokeOAuthClient",
        "security": [{ "apiKey": [] }, { "bearerAuth": [] }],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" },
            "description": "The internal row ID of the OAuth client"
          }
        ],
        "responses": {
          "204": {
            "description": "Client revoked",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            }
          },
          "401": {
            "description": "Missing or invalid token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Client not found or already revoked",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/v1/api-keys": {
      "get": {
        "tags": ["API Keys"],
        "summary": "List API keys",
        "description": "Returns all API keys for the authenticated user. The full key is never included — only the prefix and last 8 characters are shown.\n\n**Authentication:** Requires a Bearer token (API Key or OAuth token).",
        "operationId": "listApiKeys",
        "security": [{ "apiKey": [] }, { "bearerAuth": [] }],
        "responses": {
          "200": {
            "description": "List of API keys",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/ApiKeyPublic" }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      },
      "post": {
        "tags": ["API Keys"],
        "summary": "Create an API key",
        "description": "Creates a new API key. The full key is returned only once in the response — it cannot be retrieved later. Store it securely.\n\n**Authentication:** Requires a Bearer token (API Key or OAuth token).",
        "operationId": "createApiKey",
        "security": [{ "apiKey": [] }, { "bearerAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["name"],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Human-readable name for the API key",
                    "example": "Production API Key"
                  },
                  "environment": {
                    "type": "string",
                    "enum": ["live", "test"],
                    "default": "live",
                    "description": "Environment type (affects key prefix: rrule_live_ or rrule_test_)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "API key created — includes the one-time full key",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiKeyCreated" }
              }
            }
          },
          "400": {
            "description": "Invalid input (missing or invalid name)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "409": {
            "description": "API key with this name already exists",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/v1/api-keys/{id}": {
      "delete": {
        "tags": ["API Keys"],
        "summary": "Revoke an API key",
        "description": "Soft-revokes an API key. Revoked keys can no longer authenticate API requests. This action is irreversible.\n\n**Authentication:** Requires a Bearer token (API Key or OAuth token).",
        "operationId": "revokeApiKey",
        "security": [{ "apiKey": [] }, { "bearerAuth": [] }],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" },
            "description": "The internal row ID of the API key"
          }
        ],
        "responses": {
          "200": {
            "description": "API key revoked",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": { "type": "string", "example": "API key revoked" }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "API key not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API Key",
        "description": "API Key from your dashboard (recommended). Format: rrule_live_... or rrule_test_... — static keys for direct API access without token exchange."
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "OAuth 2.0 access token (alternative). Obtained via POST /oauth/token with client credentials. Expires in 1 hour."
      }
    },
    "parameters": {
      "ScheduleId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": { "type": "string" },
        "description": "Full schedule UUID or a unique UUID prefix within the caller's schedules"
      },
      "TargetId": {
        "name": "targetId",
        "in": "path",
        "required": true,
        "schema": { "type": "string" },
        "description": "Stable target id within the schedule"
      }
    },
    "headers": {
      "RateLimitLimit": {
        "description": "Maximum number of requests allowed in the current window",
        "schema": { "type": "integer", "example": 100 }
      },
      "RateLimitRemaining": {
        "description": "Number of requests remaining in the current window",
        "schema": { "type": "integer", "example": 87 }
      },
      "RateLimitReset": {
        "description": "Unix timestamp (seconds) when the rate limit window resets",
        "schema": { "type": "integer", "example": 1737800000 }
      }
    },
    "schemas": {
      "Schedule": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid", "description": "Unique schedule identifier" },
          "user_id": { "type": "string", "format": "uuid", "description": "Owner's user ID" },
          "name": { "type": "string", "description": "Human-readable schedule name. Defaults to the original input when omitted.", "example": "Daily CRM sync" },
          "status": { "type": "string", "enum": ["active", "paused"] },
          "timezone": { "type": "string", "description": "Effective IANA timezone for this schedule.", "example": "Europe/Paris" },
          "input": { "$ref": "#/components/schemas/ScheduleInput" },
          "recurrence": {
            "anyOf": [
              { "$ref": "#/components/schemas/RecurrenceJson" },
              { "type": "null" }
            ]
          },
          "event_schedule": {
            "anyOf": [
              { "$ref": "#/components/schemas/EventScheduleJson" },
              { "type": "null" }
            ],
            "description": "Canonical contextual event schedule source when the schedule is event-based."
          },
          "targets": {
            "type": "array",
            "description": "Target-specific schedule context, present for explicit targeted schedules.",
            "items": { "$ref": "#/components/schemas/ScheduleTarget" }
          },
          "explanation": {
            "type": "object",
            "properties": {
              "text": { "type": "string", "description": "Human-readable explanation of the rule", "example": "Every Monday at 9:00 AM (Europe/Paris)" },
              "confidence": { "type": "number", "minimum": 0, "maximum": 1, "description": "Confidence score of the interpretation" },
              "ambiguities": { "type": "array", "items": { "type": "string" }, "description": "List of detected ambiguities" }
            }
          },
          "webhook": {
            "type": ["object", "null"],
            "properties": {
              "url": { "type": "string", "format": "uri", "description": "Webhook endpoint URL" },
              "headers": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Custom headers (values masked after creation)" }
            }
          },
          "last_occurrence": { "type": ["string", "null"], "format": "date-time", "description": "Most recent scheduled occurrence in ISO 8601 UTC, when known.", "example": "2026-01-25T08:00:00.000Z" },
          "next_occurrence": { "type": ["string", "null"], "format": "date-time", "description": "Next scheduled occurrence in ISO 8601 UTC", "example": "2026-01-26T08:00:00.000Z" },
          "recent_executions": {
            "type": "array",
            "description": "Recent execution summaries used by the dashboard.",
            "items": {
              "type": "object",
              "properties": {
                "scheduled_for": { "type": "string", "format": "date-time" },
                "status": { "type": "string", "enum": ["sending", "success", "failed", "skipped"] },
                "response_code": { "type": ["integer", "null"] }
              }
            }
          },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "ScheduleTarget": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "Stable target id unique within the schedule. Explicit API targets provide it; natural-language targets receive a locally derived id.", "example": "paris" },
          "label": { "type": "string", "description": "Human-readable target label.", "example": "Paris audience" },
          "timezone": { "type": "string", "description": "IANA timezone used to compute this target.", "example": "Europe/Paris" },
          "recurrence": {
            "anyOf": [
              { "$ref": "#/components/schemas/RecurrenceJson" },
              { "type": "null" }
            ]
          },
          "event_schedule": {
            "anyOf": [
              { "$ref": "#/components/schemas/EventScheduleJson" },
              { "type": "null" }
            ],
            "description": "Canonical contextual event schedule source for event-based targets."
          },
          "metadata": { "type": "object", "additionalProperties": true, "description": "Target-specific metadata included in webhook payloads." }
        }
      },
      "WebhookDeliveryTarget": {
        "type": "object",
        "description": "Target context attached to a targeted schedule execution.",
        "properties": {
          "id": { "type": "string", "description": "Stable target id unique within the schedule.", "example": "paris" },
          "label": { "type": "string", "description": "Human-readable target label.", "example": "Paris audience" },
          "timezone": { "type": "string", "description": "IANA timezone used for this target execution.", "example": "Europe/Paris" },
          "metadata": { "type": "object", "additionalProperties": true, "description": "Target-specific routing metadata.", "example": { "market": "fr", "segmentId": "brevo-fr" } }
        }
      },
      "WebhookDeliveryPayload": {
        "type": "object",
        "description": "JSON body sent by rrule.net to the configured webhook URL. Untargeted schedules omit `target`; targeted schedules include it.",
        "properties": {
          "schedule_id": { "type": "string", "format": "uuid" },
          "schedule_name": { "type": "string", "example": "9am local market sends" },
          "execution_id": { "type": "string", "format": "uuid" },
          "scheduled_for": { "type": "string", "format": "date-time" },
          "executed_at": { "type": "string", "format": "date-time" },
          "timezone": { "type": "string", "example": "Europe/Paris" },
          "input": {
            "type": "object",
            "required": ["type", "value"],
            "properties": {
              "type": { "type": "string", "enum": ["rrule", "cron", "natural", "recurrence", "targets"] },
              "value": { "type": "string", "example": "9am local market sends" },
              "language": { "type": "string", "example": "en" }
            }
          },
          "target": { "$ref": "#/components/schemas/WebhookDeliveryTarget" }
        },
        "example": {
          "schedule_id": "6b02511c-d936-42cf-932f-cb1ce3832a4c",
          "schedule_name": "9am local market sends",
          "execution_id": "148bdd21-39af-45a6-8f7f-ea6b40ca39a4",
          "scheduled_for": "2026-06-04T07:00:00+00:00",
          "executed_at": "2026-06-04T07:00:02.173Z",
          "timezone": "Europe/Paris",
          "input": {
            "type": "targets",
            "value": "9am local market sends"
          },
          "target": {
            "id": "paris",
            "label": "Paris audience",
            "timezone": "Europe/Paris",
            "metadata": {
              "market": "fr",
              "segmentId": "brevo-fr"
            }
          }
        }
      },
      "ScheduleExecution": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "schedule_id": { "type": "string", "format": "uuid" },
          "execution_id": { "type": "string", "description": "Unique execution identifier sent as X-RRule-Execution-Id header" },
          "scheduled_for": { "type": "string", "format": "date-time", "description": "Planned execution time (UTC)" },
          "executed_at": { "type": "string", "format": "date-time", "description": "Actual execution time (UTC)" },
          "status": { "type": "string", "enum": ["sending", "success", "failed", "skipped"] },
          "response_code": { "type": ["integer", "null"], "description": "HTTP status code from the webhook response" },
          "response_body": { "type": ["string", "null"], "description": "Response body from the webhook (truncated)" },
          "target": {
            "description": "Target context for targeted schedule executions. Omitted for untargeted schedules.",
            "oneOf": [
              { "$ref": "#/components/schemas/WebhookDeliveryTarget" },
              { "type": "null" }
            ]
          }
        }
      },
      "OAuthTokenRequest": {
        "type": "object",
        "required": ["grant_type", "client_id", "client_secret"],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": ["client_credentials"],
            "example": "client_credentials"
          },
          "client_id": {
            "type": "string",
            "description": "OAuth2 client identifier (32 hex characters)"
          },
          "client_secret": {
            "type": "string",
            "description": "OAuth2 client secret (64 hex characters, shown only at creation)"
          },
          "resource": {
            "type": "string",
            "format": "uri",
            "enum": ["https://api.rrule.net", "https://mcp.rrule.net"],
            "default": "https://api.rrule.net"
          },
          "scope": {
            "type": "string",
            "description": "Space-separated subset of schedules:read and schedules:write.",
            "default": "schedules:read schedules:write"
          }
        }
      },
      "OAuthClientPublic": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid", "description": "Internal row ID (used for revocation)" },
          "client_id": { "type": "string", "description": "OAuth2 client identifier (32 hex characters)" },
          "name": { "type": "string", "description": "Human-readable client name" },
          "created_at": { "type": "string", "format": "date-time" },
          "active": { "type": "boolean", "description": "Whether the client is active (not revoked)" }
        }
      },
      "OAuthClientCreated": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "client_id": { "type": "string", "description": "OAuth2 client identifier (32 hex characters)" },
          "client_secret": { "type": "string", "description": "Client secret (64 hex characters) — returned only at creation time" },
          "name": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" },
          "active": { "type": "boolean" }
        }
      },
      "ApiKeyPublic": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid", "description": "Internal row ID (used for revocation)" },
          "name": { "type": "string", "description": "Human-readable key name" },
          "key": { "type": "string", "description": "Masked key for display (e.g., rrule_live_...xyz12345)", "example": "rrule_live_...xyz12345" },
          "created_at": { "type": "string", "format": "date-time" },
          "last_used_at": { "type": ["string", "null"], "format": "date-time", "description": "Last time this key was used for authentication" },
          "active": { "type": "boolean", "description": "Whether the key is active (not revoked)" },
          "revoked_at": { "type": ["string", "null"], "format": "date-time", "description": "When the key was revoked (null if active)" }
        }
      },
      "ApiKeyCreated": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "key": { "type": "string", "description": "Full API key — shown only once at creation. Format: rrule_live_... or rrule_test_... (64+ characters)", "example": "rrule_live_abc123def456..." },
          "key_prefix": { "type": "string", "description": "Key prefix (rrule_live_ or rrule_test_)", "example": "rrule_live_" },
          "key_suffix": { "type": "string", "description": "Last 8 characters of the key", "example": "xyz12345" },
          "created_at": { "type": "string", "format": "date-time" },
          "active": { "type": "boolean" }
        }
      },
      "ValidateRequest": {
        "description": "Validate either a user-facing input string, canonical recurrence JSON, or explicit @rrulenet/events schedule JSON. Provide exactly one executable source.",
        "oneOf": [
          { "$ref": "#/components/schemas/ValidateInputRequest" },
          { "$ref": "#/components/schemas/ValidateRecurrenceRequest" },
          { "$ref": "#/components/schemas/ValidateEventScheduleRequest" }
        ]
      },
      "ValidateInputRequest": {
        "type": "object",
        "title": "Validate input string",
        "required": ["input", "timezone"],
        "additionalProperties": false,
        "properties": {
          "input": { "type": "string", "description": "Scheduling rule — auto-detected as RRule, cron, or natural language", "example": "Every Monday at 9am" },
          "timezone": { "type": "string", "description": "Fallback IANA timezone. For natural-language input, one timezone or location stated explicitly in `input` takes precedence.", "example": "Europe/Paris" }
        }
      },
      "ValidateRecurrenceRequest": {
        "type": "object",
        "title": "Validate recurrence JSON",
        "required": ["recurrence", "timezone"],
        "additionalProperties": false,
        "properties": {
          "recurrence": { "$ref": "#/components/schemas/RecurrenceJson" },
          "timezone": { "type": "string", "description": "IANA timezone identifier", "example": "Europe/Paris" }
        }
      },
      "ValidateEventScheduleRequest": {
        "type": "object",
        "title": "Validate event schedule JSON",
        "required": ["event_schedule", "timezone"],
        "additionalProperties": false,
        "properties": {
          "event_schedule": { "$ref": "#/components/schemas/EventScheduleJson" },
          "timezone": { "type": "string", "description": "IANA timezone identifier returned for reference. The event schedule also carries its own projection timezone.", "example": "Europe/Paris" }
        }
      },
      "ValidateResponse": {
        "type": "object",
        "properties": {
          "valid": { "type": "boolean" },
          "input": { "$ref": "#/components/schemas/ScheduleInput" },
          "recurrence": { "$ref": "#/components/schemas/RecurrenceJson" },
          "targets": {
            "type": "array",
            "description": "Target-specific schedule context returned when natural language input maps to multiple execution targets.",
            "items": { "$ref": "#/components/schemas/ScheduleTarget" }
          },
          "event_schedule": { "$ref": "#/components/schemas/EventScheduleJson" },
          "rrule": {
            "description": "Legacy RRule projection, returned only when the schedule can be represented as one flat rule.",
            "type": "object",
            "properties": {
              "dtstart": { "type": "string", "format": "date-time", "example": "2026-01-22T08:00:00.000Z" },
              "rule": { "type": "string", "example": "FREQ=WEEKLY;BYDAY=MO;BYHOUR=9;BYMINUTE=0;BYSECOND=0" }
            }
          },
          "timezone": { "type": "string", "description": "Effective IANA timezone after natural-language precedence is applied.", "example": "Europe/Paris" },
          "explanation": {
            "type": "object",
            "properties": {
              "text": { "type": "string" },
              "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
              "ambiguities": { "type": "array", "items": { "type": "string" } }
            }
          },
          "next_occurrence": { "type": "string", "format": "date-time", "example": "2026-01-26T08:00:00.000Z" }
        }
      },
      "ScheduleInput": {
        "type": "object",
        "description": "Stable description of the persisted input path. `input.type` describes what is stored for audit and UX; `recurrence.kind` describes the executable recurrence structure.",
        "required": ["type", "value"],
        "properties": {
          "type": {
            "type": "string",
            "enum": ["rrule", "cron", "natural", "recurrence", "event_schedule"],
            "description": "Input format or source"
          },
          "value": {
            "description": "Original string for RRule, cron, and natural inputs; canonical recurrence JSON for explicit recurrence inputs; event schedule JSON for validation or simulation of contextual schedules. Targeted schedules keep the original parent input here when one was provided.",
            "oneOf": [
              { "type": "string" },
              { "$ref": "#/components/schemas/RecurrenceJson" },
              { "$ref": "#/components/schemas/EventScheduleJson" }
            ]
          },
          "language": {
            "type": "string",
            "description": "Detected language code (ISO 639-1), present for natural language inputs when available"
          }
        }
      },
      "SimulateRequest": {
        "description": "Simulate either a user-facing input string, canonical recurrence JSON, or explicit @rrulenet/events schedule JSON. Provide exactly one executable source.",
        "oneOf": [
          { "$ref": "#/components/schemas/SimulateInputRequest" },
          { "$ref": "#/components/schemas/SimulateRecurrenceRequest" },
          { "$ref": "#/components/schemas/SimulateEventScheduleRequest" }
        ]
      },
      "SimulateInputRequest": {
        "type": "object",
        "title": "Simulate input string",
        "required": ["input"],
        "additionalProperties": false,
        "properties": {
          "input": { "type": "string", "description": "Scheduling rule — auto-detected as RRule, cron, or natural language", "example": "Every Monday at 9am" },
          "timezone": { "type": "string", "description": "Fallback IANA timezone for natural-language input; defaults to UTC when omitted. One timezone or location stated explicitly in `input` takes precedence.", "example": "Europe/Paris" },
          "count": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10, "description": "Number of occurrences to simulate" }
        }
      },
      "SimulateRecurrenceRequest": {
        "type": "object",
        "title": "Simulate recurrence JSON",
        "required": ["recurrence"],
        "additionalProperties": false,
        "properties": {
          "recurrence": { "$ref": "#/components/schemas/RecurrenceJson" },
          "timezone": { "type": "string", "description": "IANA timezone returned for reference when provided", "example": "Europe/Paris" },
          "count": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10, "description": "Number of occurrences to simulate" }
        }
      },
      "SimulateEventScheduleRequest": {
        "type": "object",
        "title": "Simulate event schedule JSON",
        "required": ["event_schedule"],
        "additionalProperties": false,
        "properties": {
          "event_schedule": { "$ref": "#/components/schemas/EventScheduleJson" },
          "timezone": { "type": "string", "description": "IANA timezone returned for reference when provided. Event projection uses `event_schedule.timezone`.", "example": "Europe/Paris" },
          "count": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10, "description": "Number of occurrences to simulate" }
        }
      },
      "SimulateResponse": {
        "type": "object",
        "properties": {
          "occurrences": {
            "type": "array",
            "items": { "type": "string", "format": "date-time" },
            "description": "List of simulated occurrence timestamps in ISO 8601 UTC"
          },
          "count": { "type": "integer", "description": "Number of occurrences returned" },
          "timezone": { "type": "string", "description": "Effective IANA timezone used for interpretation. For natural-language input, this may come from an explicit timezone or location in the input rather than the request fallback." }
        }
      },
      "ParseRequest": {
        "type": "object",
        "required": ["text", "language", "timezone"],
        "properties": {
          "text": { "type": "string", "description": "Natural language schedule description", "example": "Every Monday at 9am" },
          "language": { "type": "string", "description": "BCP 47 language code used to interpret the text", "example": "en" },
          "timezone": { "type": "string", "description": "IANA timezone identifier", "example": "Europe/Paris" }
        }
      },
      "CreateScheduleRequest": {
        "description": "Create a schedule from a user-facing input string, canonical recurrence JSON, explicit event schedule JSON, or explicit execution targets. Provide exactly one executable shape. For targeted schedules, optional top-level `input` is the original user-facing text only.",
        "oneOf": [
          { "$ref": "#/components/schemas/CreateScheduleFromInputRequest" },
          { "$ref": "#/components/schemas/CreateScheduleFromRecurrenceRequest" },
          { "$ref": "#/components/schemas/CreateScheduleFromEventScheduleRequest" },
          { "$ref": "#/components/schemas/CreateScheduleFromTargetsRequest" }
        ]
      },
      "CreateScheduleFromInputRequest": {
        "type": "object",
        "title": "Create from input string",
        "required": ["input", "timezone", "webhook"],
        "additionalProperties": false,
        "properties": {
          "input": { "type": "string", "description": "Scheduling rule — auto-detected as RRule, cron, or natural language", "example": "FREQ=WEEKLY;BYDAY=MO;BYHOUR=9" },
          "name": { "type": "string", "description": "Optional human-readable schedule name. Defaults to input when omitted.", "example": "Weekly operations sync", "maxLength": 160 },
          "timezone": { "type": "string", "description": "Fallback IANA timezone. For natural-language input, one timezone or location stated explicitly in `input` takes precedence and is persisted.", "example": "Europe/Paris" },
          "webhook": { "$ref": "#/components/schemas/WebhookRequest" }
        }
      },
      "CreateScheduleFromRecurrenceRequest": {
        "type": "object",
        "title": "Create from recurrence JSON",
        "required": ["recurrence", "timezone", "webhook"],
        "additionalProperties": false,
        "properties": {
          "recurrence": { "$ref": "#/components/schemas/RecurrenceJson" },
          "name": { "type": "string", "description": "Optional human-readable schedule name. Defaults to the serialized recurrence when omitted.", "example": "Business hours webhook", "maxLength": 160 },
          "timezone": { "type": "string", "description": "IANA timezone identifier", "example": "Europe/Paris" },
          "webhook": { "$ref": "#/components/schemas/WebhookRequest" }
        }
      },
      "CreateScheduleFromEventScheduleRequest": {
        "type": "object",
        "title": "Create from event schedule JSON",
        "required": ["event_schedule", "timezone", "webhook"],
        "additionalProperties": false,
        "properties": {
          "event_schedule": { "$ref": "#/components/schemas/EventScheduleJson" },
          "name": { "type": "string", "description": "Optional human-readable schedule name. Defaults to the serialized event schedule when omitted.", "example": "Maintenance customer triggers", "maxLength": 160 },
          "timezone": { "type": "string", "description": "IANA timezone identifier returned on the parent schedule. The event schedule also carries its own projection timezone.", "example": "Europe/Paris" },
          "webhook": { "$ref": "#/components/schemas/WebhookRequest" }
        }
      },
      "CreateScheduleFromTargetsRequest": {
        "type": "object",
        "title": "Create from explicit targets",
        "description": "Create one logical schedule with multiple target-specific schedule sources. Each target gets its own next occurrence and can attach metadata to webhook payloads. Targets can use ordinary recurrence sources or explicit @rrulenet/events contextual schedules. Optional top-level `input` is stored as the original user-facing description for audit and dashboard UX only; it is not parsed or used to compute occurrences.",
        "required": ["targets", "timezone", "webhook"],
        "additionalProperties": false,
        "properties": {
          "input": { "type": "string", "description": "Original user-facing description for audit and UX only. Executable sources must be provided on each target.", "example": "Send every day at 9am local time in Paris, London, Moscow, and Perth" },
          "targets": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": { "$ref": "#/components/schemas/ScheduleTargetRequest" }
          },
          "name": { "type": "string", "description": "Optional human-readable schedule name.", "example": "9am local market sends", "maxLength": 160 },
          "timezone": { "type": "string", "description": "Dashboard display timezone for the parent logical schedule.", "example": "Europe/Paris" },
          "webhook": { "$ref": "#/components/schemas/WebhookRequest" }
        },
        "example": {
          "name": "9am local market sends",
          "input": "Send every day at 9am local time in Paris, London, Moscow, and Perth",
          "timezone": "Europe/Paris",
          "targets": [
            {
              "id": "paris",
              "label": "Paris audience",
              "input": "Every day at 9am",
              "timezone": "Europe/Paris",
              "metadata": { "market": "fr", "audience": "paris" }
            },
            {
              "id": "london",
              "label": "London audience",
              "input": "Every day at 9am",
              "timezone": "Europe/London",
              "metadata": { "market": "uk", "audience": "london" }
            },
            {
              "id": "moscow",
              "label": "Moscow audience",
              "input": "Every day at 9am",
              "timezone": "Europe/Moscow",
              "metadata": { "market": "ru", "audience": "moscow" }
            },
            {
              "id": "perth",
              "label": "Perth audience",
              "input": "Every day at 9am",
              "timezone": "Australia/Perth",
              "metadata": { "market": "au", "audience": "perth" }
            }
          ],
          "webhook": { "url": "https://example.com/webhooks/rrule" }
        }
      },
      "ScheduleTargetRequest": {
        "description": "A target-specific schedule input. Provide exactly one of `input`, `recurrence`, or `event_schedule`. Event-relative trigger schedules must omit recurrence.",
        "oneOf": [
          { "$ref": "#/components/schemas/ScheduleTargetFromInputRequest" },
          { "$ref": "#/components/schemas/ScheduleTargetFromRecurrenceRequest" },
          { "$ref": "#/components/schemas/ScheduleTargetFromEventScheduleRequest" }
        ]
      },
      "ScheduleTargetFromInputRequest": {
        "type": "object",
        "title": "Target from input string",
        "required": ["id", "input", "timezone"],
        "additionalProperties": false,
        "properties": {
          "id": { "type": "string", "pattern": "^[a-zA-Z0-9_-]+$", "description": "Stable target id unique within the schedule. `default` is reserved.", "example": "paris" },
          "label": { "type": "string", "description": "Human-readable target label.", "example": "Paris audience", "maxLength": 160 },
          "input": { "type": "string", "description": "Target schedule input — auto-detected as RRule, cron, or natural language.", "example": "Every day at 9am" },
          "timezone": { "type": "string", "description": "IANA timezone used to interpret this target.", "example": "Europe/Paris" },
          "metadata": { "type": "object", "additionalProperties": true, "description": "Target-specific JSON metadata included in webhook payloads.", "example": { "market": "fr" } }
        }
      },
      "ScheduleTargetFromRecurrenceRequest": {
        "type": "object",
        "title": "Target from recurrence JSON",
        "required": ["id", "recurrence", "timezone"],
        "additionalProperties": false,
        "properties": {
          "id": { "type": "string", "pattern": "^[a-zA-Z0-9_-]+$", "description": "Stable target id unique within the schedule. `default` is reserved.", "example": "paris" },
          "label": { "type": "string", "description": "Human-readable target label.", "example": "Paris audience", "maxLength": 160 },
          "recurrence": { "$ref": "#/components/schemas/RecurrenceJson" },
          "timezone": { "type": "string", "description": "IANA timezone used to interpret this target.", "example": "Europe/Paris" },
          "metadata": { "type": "object", "additionalProperties": true, "description": "Target-specific JSON metadata included in webhook payloads.", "example": { "market": "fr" } }
        }
      },
      "ScheduleTargetFromEventScheduleRequest": {
        "type": "object",
        "title": "Target from contextual event schedule JSON",
        "description": "Target source for explicit @rrulenet/events contextual schedules. For event-relative triggers (`transform.kind = event-relative`), `event_schedule` must not include a recurrence; occurrences are generated from the embedded event snapshot and target timezone.",
        "required": ["id", "event_schedule", "timezone"],
        "additionalProperties": false,
        "properties": {
          "id": { "type": "string", "pattern": "^[a-zA-Z0-9_-]+$", "description": "Stable target id unique within the schedule. `default` is reserved.", "example": "paris" },
          "label": { "type": "string", "description": "Human-readable target label.", "example": "Paris customers", "maxLength": 160 },
          "event_schedule": { "$ref": "#/components/schemas/EventScheduleJson" },
          "timezone": { "type": "string", "description": "IANA timezone used to interpret this target.", "example": "Europe/Paris" },
          "metadata": { "type": "object", "additionalProperties": true, "description": "Target-specific JSON metadata included in webhook payloads.", "example": { "market": "fr" } }
        }
      },
      "WebhookRequest": {
        "type": "object",
        "description": "Webhook endpoint configuration. rrule.net sends a JSON body matching `WebhookDeliveryPayload` and includes execution headers such as `X-RRule-Schedule-Id`, `X-RRule-Execution-Id`, `X-RRule-Scheduled-For`, and `X-RRule-Target-Id` for targeted schedules.",
        "required": ["url"],
        "properties": {
          "url": { "type": "string", "format": "uri", "description": "Webhook URL (http or https)" },
          "headers": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Custom headers to include in webhook requests" }
        }
      },
      "EventScheduleJson": {
        "oneOf": [
          { "$ref": "#/components/schemas/EventRecurrenceScheduleJson" },
          { "$ref": "#/components/schemas/EventRelativeScheduleJson" }
        ]
      },
      "EventRecurrenceScheduleJson": {
        "type": "object",
        "description": "Explicit @rrulenet/events schedule JSON for `during` and `window` transforms. These schedules project a recurrence through event windows.",
        "required": ["kind", "timezone", "events", "recurrence", "transform"],
        "additionalProperties": false,
        "properties": {
          "kind": { "const": "event-schedule" },
          "timezone": { "type": "string", "description": "IANA timezone used for event-date projection", "example": "America/New_York" },
          "events": { "$ref": "#/components/schemas/EventSetJson" },
          "recurrence": { "$ref": "#/components/schemas/RecurrenceJson" },
          "transform": { "$ref": "#/components/schemas/EventRecurrenceTransformJson" }
        }
      },
      "EventRelativeScheduleJson": {
        "type": "object",
        "description": "Explicit @rrulenet/events schedule JSON for event-relative trigger transforms. These schedules generate occurrences directly from event-local dates and do not include recurrence.",
        "required": ["kind", "timezone", "events", "transform"],
        "additionalProperties": false,
        "properties": {
          "kind": { "const": "event-schedule" },
          "timezone": { "type": "string", "description": "IANA timezone used for event-relative trigger projection", "example": "Australia/Perth" },
          "events": { "$ref": "#/components/schemas/EventSetJson" },
          "transform": { "$ref": "#/components/schemas/EventRelativeTransformJson" }
        }
      },
      "EventSetJson": {
        "type": "object",
        "required": ["kind", "id", "version", "members"],
        "additionalProperties": false,
        "properties": {
          "kind": { "const": "event-set" },
          "id": { "type": "string", "example": "retail_weekend_2026" },
          "version": { "type": "string", "example": "2026.1" },
          "source": { "type": "string", "description": "Optional source label or catalogue identifier" },
          "timezone": { "type": ["string", "null"], "description": "Optional source timezone for the event set" },
          "metadata": { "type": "object", "additionalProperties": true },
          "members": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/EventSetMemberJson" },
            "minItems": 1
          }
        }
      },
      "EventSetMemberJson": {
        "oneOf": [
          { "$ref": "#/components/schemas/EventDateMemberJson" },
          { "$ref": "#/components/schemas/EventPointMemberJson" },
          { "$ref": "#/components/schemas/EventIntervalMemberJson" }
        ]
      },
      "EventDateMemberJson": {
        "type": "object",
        "required": ["kind", "id", "date"],
        "additionalProperties": false,
        "properties": {
          "kind": { "const": "date" },
          "id": { "type": "string", "example": "black_friday_2026" },
          "date": { "type": "string", "format": "date", "example": "2026-11-27" },
          "label": { "type": "string", "example": "Black Friday 2026" },
          "metadata": { "type": "object", "additionalProperties": true }
        }
      },
      "EventPointMemberJson": {
        "type": "object",
        "required": ["kind", "id", "at"],
        "additionalProperties": false,
        "properties": {
          "kind": { "const": "point" },
          "id": { "type": "string" },
          "at": { "type": "string", "format": "date-time" },
          "label": { "type": "string" },
          "metadata": { "type": "object", "additionalProperties": true }
        }
      },
      "EventIntervalMemberJson": {
        "type": "object",
        "required": ["kind", "id", "start", "end"],
        "additionalProperties": false,
        "properties": {
          "kind": { "const": "interval" },
          "id": { "type": "string" },
          "start": { "type": "string", "format": "date-time" },
          "end": { "type": "string", "format": "date-time" },
          "label": { "type": "string" },
          "metadata": { "type": "object", "additionalProperties": true }
        }
      },
      "EventTransformJson": {
        "oneOf": [
          { "$ref": "#/components/schemas/EventRecurrenceTransformJson" },
          { "$ref": "#/components/schemas/EventRelativeTransformJson" }
        ]
      },
      "EventRecurrenceTransformJson": {
        "oneOf": [
          {
            "type": "object",
            "required": ["kind"],
            "additionalProperties": false,
            "properties": {
              "kind": { "const": "during" }
            }
          },
          {
            "type": "object",
            "required": ["kind"],
            "additionalProperties": false,
            "properties": {
              "kind": { "const": "window" },
              "before": { "$ref": "#/components/schemas/EventDurationJson" },
              "after": { "$ref": "#/components/schemas/EventDurationJson" }
            }
          }
        ]
      },
      "EventRelativeTransformJson": {
        "type": "object",
        "required": ["kind", "triggers"],
        "additionalProperties": false,
        "properties": {
          "kind": { "const": "event-relative" },
          "triggers": {
            "type": "array",
            "minItems": 1,
            "items": { "$ref": "#/components/schemas/EventRelativeTriggerJson" }
          }
        },
        "examples": [
          {
            "kind": "event-relative",
            "triggers": [
              { "before": { "hours": 2 } },
              { "before": { "minutes": 15 } },
              { "at": "start" },
              { "at": "end" }
            ]
          }
        ]
      },
      "EventRelativeTriggerJson": {
        "oneOf": [
          { "$ref": "#/components/schemas/EventRelativeCalendarBeforeTriggerJson" },
          { "$ref": "#/components/schemas/EventRelativeCalendarAfterTriggerJson" },
          { "$ref": "#/components/schemas/EventRelativeElapsedBeforeTriggerJson" },
          { "$ref": "#/components/schemas/EventRelativeElapsedAfterTriggerJson" },
          { "$ref": "#/components/schemas/EventRelativeAtTriggerJson" }
        ]
      },
      "EventRelativeCalendarBeforeTriggerJson": {
        "type": "object",
        "required": ["before", "time"],
        "additionalProperties": false,
        "properties": {
          "before": { "$ref": "#/components/schemas/EventCalendarDurationJson" },
          "time": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}(:\\d{2})?$",
            "example": "09:00"
          }
        }
      },
      "EventRelativeCalendarAfterTriggerJson": {
        "type": "object",
        "required": ["after", "time"],
        "additionalProperties": false,
        "properties": {
          "after": { "$ref": "#/components/schemas/EventCalendarDurationJson" },
          "time": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}(:\\d{2})?$",
            "example": "09:00"
          }
        }
      },
      "EventRelativeElapsedBeforeTriggerJson": {
        "type": "object",
        "required": ["before"],
        "additionalProperties": false,
        "properties": {
          "before": { "$ref": "#/components/schemas/EventElapsedDurationJson" }
        },
        "example": { "before": { "minutes": 15 } }
      },
      "EventRelativeElapsedAfterTriggerJson": {
        "type": "object",
        "required": ["after"],
        "additionalProperties": false,
        "properties": {
          "after": { "$ref": "#/components/schemas/EventElapsedDurationJson" }
        },
        "example": { "after": { "hours": 1, "minutes": 30 } }
      },
      "EventRelativeAtTriggerJson": {
        "type": "object",
        "required": ["at"],
        "additionalProperties": false,
        "properties": {
          "at": {
            "type": "string",
            "enum": ["start", "end"]
          }
        },
        "example": { "at": "start" }
      },
      "EventCalendarDurationJson": {
        "type": "object",
        "required": ["days"],
        "additionalProperties": false,
        "properties": {
          "days": { "type": "integer", "minimum": 0, "example": 7 }
        }
      },
      "EventElapsedDurationJson": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "hours": { "type": "integer", "minimum": 0, "example": 2 },
          "minutes": { "type": "integer", "minimum": 0, "example": 15 }
        },
        "anyOf": [
          {
            "required": ["hours"],
            "properties": {
              "hours": { "type": "integer", "minimum": 1 }
            }
          },
          {
            "required": ["minutes"],
            "properties": {
              "minutes": { "type": "integer", "minimum": 1 }
            }
          }
        ]
      },
      "EventDurationJson": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "days": { "type": "integer", "minimum": 0, "example": 10 }
        }
      },
      "RecurrenceJson": {
        "type": "object",
        "description": "Canonical @rrulenet/recurrence JSON. Supports simple inputs, explicit dates, and composed schedules such as unions, intersections, and differences.",
        "additionalProperties": true,
        "examples": [
          {
            "kind": "input",
            "start": "2026-01-01T09:00:00+01:00[Europe/Paris]",
            "tzid": "Europe/Paris",
            "include": [
              {
                "rule": {
                  "freq": "WEEKLY",
                  "byDay": ["MO", "TU", "WE", "TH", "FR"],
                  "byHour": [9],
                  "byMinute": [0],
                  "bySecond": [0]
                }
              }
            ],
            "exclude": []
          },
          {
            "kind": "union",
            "expressions": [
              {
                "kind": "input",
                "start": "2026-01-01T09:00:00+01:00[Europe/Paris]",
                "tzid": "Europe/Paris",
                "include": [
                  {
                    "rule": {
                      "freq": "WEEKLY",
                      "byDay": ["MO", "TU", "WE", "TH", "FR"],
                      "byHour": [9],
                      "byMinute": [0],
                      "bySecond": [0]
                    }
                  }
                ],
                "exclude": []
              },
              {
                "kind": "input",
                "start": "2026-01-01T10:00:00+01:00[Europe/Paris]",
                "tzid": "Europe/Paris",
                "include": [
                  {
                    "rule": {
                      "freq": "WEEKLY",
                      "byDay": ["SA", "SU"],
                      "byHour": [10],
                      "byMinute": [0],
                      "bySecond": [0]
                    }
                  }
                ],
                "exclude": []
              }
            ]
          }
        ]
      },
      "UpdateScheduleRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": ["string", "null"],
            "description": "Human-readable schedule name. Send null to reset to the original input.",
            "example": "Daily CRM sync",
            "maxLength": 160
          },
          "webhook": {
            "type": "object",
            "properties": {
              "url": { "type": "string", "format": "uri" },
              "headers": { "type": "object", "additionalProperties": { "type": "string" } }
            }
          }
        }
      },
      "UpdateScheduleTargetRequest": {
        "type": "object",
        "description": "Mutable target context. Timing fields are intentionally omitted: target id, timezone, recurrence, and event schedule cannot be changed after creation.",
        "additionalProperties": false,
        "minProperties": 1,
        "properties": {
          "label": {
            "type": "string",
            "description": "Human-readable target label.",
            "example": "Paris audience",
            "maxLength": 160
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Target-specific JSON metadata included in future webhook payloads.",
            "example": { "market": "fr", "segmentId": "brevo-fr" }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "description": "Human-readable error message" },
          "code": { "type": "string", "description": "Stable machine-readable error code" },
          "message": { "type": "string", "description": "Short user-facing message when available" },
          "details": { "type": "string", "description": "Additional context or validation details" }
        }
      }
    }
  }
}
