{
  "openapi": "3.0.4",
  "info": {
    "title": "Kirken ACTS Vejle Public API",
    "version": "1.1.0",
    "description": "Kirken ACTS Vejle public read-only API. Stable JSON endpoints use /api/v1; breaking changes use a new major URL. At least 90 days notice before retiring a version, with RFC 9745 Deprecation and RFC 8594 Sunset headers on affected responses. No retirement is scheduled. Additive fields may appear in v1; ignore unknown response fields. JSON burst limits are per IP, route and server instance, not distributed; see /docs. No credentials, public booking, payment or administration API.",
    "contact": {
      "name": "Kirken ACTS Vejle",
      "url": "https://actsvejle.dk/kontakt"
    }
  },
  "servers": [
    {
      "url": "https://actsvejle.dk",
      "description": "Kirken ACTS Vejle canonical public website"
    }
  ],
  "externalDocs": {
    "description": "Kirken ACTS Vejle API documentation and examples",
    "url": "https://actsvejle.dk/docs"
  },
  "security": [],
  "tags": [
    {
      "name": "Discovery",
      "description": "Find public API resources and machine-readable documentation."
    },
    {
      "name": "Calendar",
      "description": "Read the existing public iCalendar feed, including recurrence and exceptions."
    },
    {
      "name": "Events",
      "description": "Typed JSON of public event occurrences with localization, recurrence, cancellations and pagination."
    }
  ],
  "paths": {
    "/api": {
      "get": {
        "operationId": "getPublicApiResources",
        "summary": "Discover Kirken ACTS Vejle public API resources",
        "description": "Compatibility resource directory linking to stable /api/v1, typed events, documentation, OpenAPI, iCalendar and Markdown. Existing fields and URLs are retained. All methods consume one unit of the JSON burst quota. GET and HEAD are read-only; OPTIONS reports allowed methods. JSON responses are no-store.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "Typed JSON response. Ignore unknown fields added within v1. No credentials required.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResources"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/calendar": {
      "get": {
        "operationId": "getPublicCalendar",
        "summary": "Read the Kirken ACTS Vejle calendar",
        "description": "Existing iCalendar subscription feed by default, retaining RFC 5545 recurrence, exceptions, Europe/Copenhagen timezone data and five-minute caching. Missing Accept, */* and equal preferences select iCalendar. A higher quality preference for application/json selects the same typed occurrence JSON as /api/v1/events with its date, pagination, validation and per-instance burst policy. Prefer /api/v1/events for stable JSON integrations. JSON is no-store; iCalendar is outside the JSON burst quota. Unknown query parameters are ignored for iCalendar and rejected for JSON. GET and HEAD are read-only; OPTIONS reports allowed methods.",
        "tags": [
          "Calendar"
        ],
        "parameters": [
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Language of calendar titles and descriptions. Defaults to Danish; unsupported values retain the existing Danish fallback. Missing translations fall back to the available event text.",
            "schema": {
              "type": "string",
              "enum": [
                "da",
                "en",
                "de",
                "es"
              ],
              "default": "da"
            },
            "example": "en"
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "description": "Category IDs from public calendar subscription links. Repeat category= for multiple IDs; selects matching events, including category aliases. At most 64 category and type values combined. Unknown valid IDs return an empty selection, never all events.",
            "schema": {
              "type": "array",
              "minItems": 1,
              "maxItems": 64,
              "items": {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[a-zA-Z0-9_-]{1,128}$"
              }
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "description": "Legacy filters for uncategorized events only. Repeat type= for multiple values. At most 64 category and type values combined; categorized events keep their category filters.",
            "schema": {
              "type": "array",
              "minItems": 1,
              "maxItems": 64,
              "items": {
                "type": "string",
                "enum": [
                  "service",
                  "kids",
                  "groups",
                  "worship",
                  "special"
                ]
              }
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inclusive occurrence start date YYYY-MM-DD, years 1970 through 9998. Defaults to today in Europe/Copenhagen. from <= to; at most 366 inclusive days. JSON representation only; ignored for iCalendar.",
            "schema": {
              "type": "string",
              "format": "date",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Inclusive occurrence start date YYYY-MM-DD, years 1970 through 9998. Default from + 90 days. Range at most 366 inclusive days. JSON representation only; ignored for iCalendar.",
            "schema": {
              "type": "string",
              "format": "date",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum occurrences per page. Decimal integer; duplicates are rejected. JSON representation only; ignored for iCalendar.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Zero-based page offset. Continue with pagination.nextOffset until null. Use the same filters and dates for every page. Results may change between requests. JSON representation only; ignored for iCalendar.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 9007199254740991,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "RFC 5545 iCalendar subscription feed. A valid selection with no events still returns a complete VCALENDAR. Existing subscription URLs and the default feed format are preserved. With Accept: application/json, returns EventPage JSON instead.",
            "headers": {
              "Cache-Control": {
                "description": "iCalendar: public, max-age=300, s-maxage=300; JSON: no-store.",
                "schema": {
                  "type": "string",
                  "example": "public, max-age=300, s-maxage=300"
                }
              },
              "Content-Disposition": {
                "description": "Existing calendar subscription filename.",
                "schema": {
                  "type": "string",
                  "example": "inline; filename=\"kirken-acts-vejle.ics\""
                }
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "Vary": {
                "description": "Representations vary by Accept.",
                "schema": {
                  "type": "string",
                  "example": "Accept"
                }
              }
            },
            "content": {
              "text/calendar": {
                "schema": {
                  "type": "string",
                  "description": "UTF-8 iCalendar text with CRLF content lines, recurrence rules, exceptions and timezone definitions."
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventPage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid calendar selection: an empty or malformed category, an unsupported legacy type, or more than 64 combined filter values. Fix the selection before retrying. JSON requests may also return INVALID_EVENT_QUERY for invalid language, dates, pagination, duplicate scalar or unknown parameters.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "example": {
                  "type": "about:blank",
                  "title": "Bad Request",
                  "status": 400,
                  "detail": "Invalid calendar selection.",
                  "instance": "/api/calendar",
                  "code": "INVALID_CALENDAR_SELECTION",
                  "hint": "Use valid category IDs or types. Send at most 64 category and type values combined; omit both filters to read all events. See /docs."
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "Calendar data is temporarily unavailable. Do not treat an outage as an empty calendar. Wait for Retry-After before retrying.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string",
                  "example": "300"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1": {
      "get": {
        "operationId": "getV1PublicApiResources",
        "summary": "Discover Kirken ACTS Vejle API version 1 and policy",
        "description": "Return canonical public resource links and the machine-readable version, deprecation and per-instance burst policy. Breaking changes use a new major URL; no retirement is scheduled. All methods consume one burst quota unit; JSON is no-store. GET and HEAD are read-only; OPTIONS reports allowed methods.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "Typed JSON response. Ignore unknown fields added within v1. No credentials required.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VersionedApiResources"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/events": {
      "get": {
        "operationId": "listV1PublicEvents",
        "summary": "List localized Kirken ACTS Vejle events as typed JSON",
        "description": "Read public concrete occurrences in an inclusive start-date range, ordered by date, time and ID. Recurrence, exclusions, pauses and date overrides follow the website calendar. At most 366 inclusive days and 100 occurrences per page. Times are local to Europe/Copenhagen, including daylight saving; end times are only returned when published. Default range is Copenhagen today through 90 days later. Repeat only category and type; unknown parameters, duplicate scalar parameters and invalid values return 400. Empty valid selections return events: [] with total 0; outages return 503. JSON is no-store. All methods consume one quota unit. GET and HEAD are read-only; OPTIONS reports allowed methods.",
        "tags": [
          "Events"
        ],
        "parameters": [
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Requested event language; default da. Unsupported values return 400. Missing translations fall back to English, then Danish.",
            "schema": {
              "type": "string",
              "enum": [
                "da",
                "en",
                "de",
                "es"
              ],
              "default": "da"
            },
            "example": "en"
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "description": "Category IDs from public calendar subscription links. Repeat category= for multiple IDs; selects matching events, including category aliases. At most 64 category and type values combined. Unknown valid IDs return an empty selection, never all events.",
            "schema": {
              "type": "array",
              "minItems": 1,
              "maxItems": 64,
              "items": {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[a-zA-Z0-9_-]{1,128}$"
              }
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "description": "Legacy filters for uncategorized events only. Repeat type= for multiple values. At most 64 category and type values combined; categorized events keep their category filters.",
            "schema": {
              "type": "array",
              "minItems": 1,
              "maxItems": 64,
              "items": {
                "type": "string",
                "enum": [
                  "service",
                  "kids",
                  "groups",
                  "worship",
                  "special"
                ]
              }
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inclusive occurrence start date YYYY-MM-DD, years 1970 through 9998. Defaults to today in Europe/Copenhagen. from <= to; at most 366 inclusive days.",
            "schema": {
              "type": "string",
              "format": "date",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Inclusive occurrence start date YYYY-MM-DD, years 1970 through 9998. Default from + 90 days. Range at most 366 inclusive days.",
            "schema": {
              "type": "string",
              "format": "date",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum occurrences per page. Decimal integer; duplicates are rejected.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Zero-based page offset. Continue with pagination.nextOffset until null. Use the same filters and dates for every page. Results may change between requests.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 9007199254740991,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Typed JSON response. Ignore unknown fields added within v1. No credentials required.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidEventQuery"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/CalendarUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ApiResources": {
        "type": "object",
        "required": [
          "name",
          "version",
          "authentication",
          "documentation",
          "openapi",
          "calendar",
          "markdown",
          "languages",
          "apiVersion",
          "versionedApi",
          "events",
          "versioningPolicy"
        ],
        "properties": {
          "name": {
            "type": "string",
            "example": "Kirken ACTS Vejle Public API"
          },
          "version": {
            "type": "string",
            "example": "1.1.0"
          },
          "authentication": {
            "type": "string",
            "enum": [
              "none"
            ],
            "description": "The documented public read operations require no credentials."
          },
          "documentation": {
            "type": "string",
            "format": "uri",
            "example": "https://actsvejle.dk/docs"
          },
          "openapi": {
            "type": "string",
            "format": "uri",
            "example": "https://actsvejle.dk/openapi.json"
          },
          "calendar": {
            "type": "string",
            "format": "uri",
            "example": "https://actsvejle.dk/api/calendar"
          },
          "markdown": {
            "type": "string",
            "format": "uri",
            "example": "https://actsvejle.dk/index.md"
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "da",
                "en",
                "de",
                "es"
              ]
            },
            "example": [
              "da",
              "en",
              "de",
              "es"
            ]
          },
          "apiVersion": {
            "type": "string",
            "enum": [
              "1"
            ]
          },
          "versionedApi": {
            "type": "string",
            "format": "uri",
            "example": "https://actsvejle.dk/api/v1"
          },
          "events": {
            "type": "string",
            "format": "uri",
            "example": "https://actsvejle.dk/api/v1/events"
          },
          "versioningPolicy": {
            "type": "string",
            "format": "uri",
            "example": "https://actsvejle.dk/docs#docs-versioning"
          }
        }
      },
      "Problem": {
        "type": "object",
        "description": "RFC 9457 Problem Details with stable error code and resolution hint. Unknown extension members must be ignored. Error responses are not cached; backend exception details are never exposed.",
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "instance",
          "code",
          "hint"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "enum": [
              "about:blank"
            ]
          },
          "title": {
            "type": "string",
            "description": "The standard HTTP reason phrase."
          },
          "status": {
            "type": "integer",
            "minimum": 400,
            "maximum": 599
          },
          "detail": {
            "type": "string",
            "description": "A safe description of the failure."
          },
          "instance": {
            "type": "string",
            "description": "The request pathname, with query values omitted."
          },
          "code": {
            "type": "string",
            "enum": [
              "INVALID_CALENDAR_SELECTION",
              "CALENDAR_UNAVAILABLE",
              "METHOD_NOT_ALLOWED",
              "API_NOT_FOUND",
              "INVALID_EVENT_QUERY",
              "RATE_LIMITED"
            ]
          },
          "hint": {
            "type": "string",
            "description": "How the caller can resolve or retry the request."
          }
        }
      },
      "ApiPolicy": {
        "type": "object",
        "required": [
          "version",
          "versioning",
          "deprecationNoticeDays",
          "deprecation",
          "rateLimit"
        ],
        "properties": {
          "version": {
            "type": "string",
            "enum": [
              "1"
            ]
          },
          "versioning": {
            "type": "string",
            "description": "URL major version policy, including additive changes."
          },
          "deprecationNoticeDays": {
            "type": "integer",
            "minimum": 90,
            "example": 90
          },
          "deprecation": {
            "type": "string",
            "description": "Retirement notice policy and current status."
          },
          "rateLimit": {
            "type": "object",
            "required": [
              "scope",
              "requests",
              "windowSeconds"
            ],
            "properties": {
              "scope": {
                "type": "string",
                "enum": [
                  "per client IP, per route, per server instance"
                ]
              },
              "requests": {
                "type": "integer",
                "enum": [
                  120
                ]
              },
              "windowSeconds": {
                "type": "integer",
                "enum": [
                  60
                ]
              }
            }
          }
        }
      },
      "VersionedApiResources": {
        "type": "object",
        "required": [
          "name",
          "version",
          "authentication",
          "documentation",
          "openapi",
          "calendar",
          "markdown",
          "languages",
          "apiVersion",
          "versionedApi",
          "events",
          "versioningPolicy",
          "policy"
        ],
        "properties": {
          "name": {
            "type": "string",
            "example": "Kirken ACTS Vejle Public API"
          },
          "version": {
            "type": "string",
            "example": "1.1.0"
          },
          "authentication": {
            "type": "string",
            "enum": [
              "none"
            ],
            "description": "The documented public read operations require no credentials."
          },
          "documentation": {
            "type": "string",
            "format": "uri",
            "example": "https://actsvejle.dk/docs"
          },
          "openapi": {
            "type": "string",
            "format": "uri",
            "example": "https://actsvejle.dk/openapi.json"
          },
          "calendar": {
            "type": "string",
            "format": "uri",
            "example": "https://actsvejle.dk/api/calendar"
          },
          "markdown": {
            "type": "string",
            "format": "uri",
            "example": "https://actsvejle.dk/index.md"
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "da",
                "en",
                "de",
                "es"
              ]
            },
            "example": [
              "da",
              "en",
              "de",
              "es"
            ]
          },
          "apiVersion": {
            "type": "string",
            "enum": [
              "1"
            ]
          },
          "versionedApi": {
            "type": "string",
            "format": "uri",
            "example": "https://actsvejle.dk/api/v1"
          },
          "events": {
            "type": "string",
            "format": "uri",
            "example": "https://actsvejle.dk/api/v1/events"
          },
          "versioningPolicy": {
            "type": "string",
            "format": "uri",
            "example": "https://actsvejle.dk/docs#docs-versioning"
          },
          "policy": {
            "$ref": "#/components/schemas/ApiPolicy"
          }
        }
      },
      "PublicEvent": {
        "type": "object",
        "description": "A concrete public occurrence. Recurrence rules are expanded and cancellations, pauses and date overrides applied. No raw backend document, admin data or registration keys.",
        "required": [
          "id",
          "title",
          "description",
          "date",
          "time",
          "location",
          "categories"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Public occurrence ID; recurring occurrence IDs include their date."
          },
          "title": {
            "type": "string",
            "description": "Requested language with English then Danish fallback."
          },
          "description": {
            "type": "string",
            "description": "Public description in the requested language, or English then Danish. May be empty."
          },
          "date": {
            "type": "string",
            "format": "date",
            "description": "Occurrence start date in Europe/Copenhagen."
          },
          "time": {
            "type": "string",
            "description": "Published local start time in Europe/Copenhagen, normally HH:mm; empty if no time is published."
          },
          "endDate": {
            "type": "string",
            "format": "date",
            "description": "Published end date, if specified."
          },
          "endTime": {
            "type": "string",
            "description": "Published local end time, if specified. No duration is inferred."
          },
          "location": {
            "type": "string"
          },
          "speaker": {
            "type": "string"
          },
          "notice": {
            "type": "string",
            "description": "Public localized special notice."
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "label"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "label": {
                  "type": "string",
                  "description": "Danish category name for da; otherwise English with Danish fallback."
                }
              }
            }
          }
        }
      },
      "EventPage": {
        "type": "object",
        "required": [
          "apiVersion",
          "language",
          "timezone",
          "range",
          "pagination",
          "events"
        ],
        "properties": {
          "apiVersion": {
            "type": "string",
            "enum": [
              "1"
            ]
          },
          "language": {
            "type": "string",
            "enum": [
              "da",
              "en",
              "de",
              "es"
            ]
          },
          "timezone": {
            "type": "string",
            "enum": [
              "Europe/Copenhagen"
            ]
          },
          "range": {
            "type": "object",
            "required": [
              "from",
              "to"
            ],
            "properties": {
              "from": {
                "type": "string",
                "format": "date"
              },
              "to": {
                "type": "string",
                "format": "date"
              }
            }
          },
          "pagination": {
            "type": "object",
            "required": [
              "limit",
              "offset",
              "total",
              "nextOffset"
            ],
            "properties": {
              "limit": {
                "type": "integer",
                "minimum": 1,
                "maximum": 100
              },
              "offset": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "total": {
                "type": "integer",
                "minimum": 0
              },
              "nextOffset": {
                "type": "integer",
                "minimum": 0,
                "nullable": true,
                "description": "Use as offset with the same query until null. Data may change between pages; this is not a snapshot."
              }
            }
          },
          "events": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/PublicEvent"
            }
          }
        }
      }
    },
    "responses": {
      "MethodNotAllowed": {
        "description": "This endpoint is read-only. Retry using GET; HEAD and OPTIONS are also supported.",
        "headers": {
          "Allow": {
            "description": "The methods supported by this endpoint.",
            "schema": {
              "type": "string",
              "enum": [
                "GET, HEAD, OPTIONS"
              ]
            }
          },
          "API-Version": {
            "$ref": "#/components/headers/ApiVersion"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "Deprecation": {
            "$ref": "#/components/headers/Deprecation"
          },
          "Sunset": {
            "$ref": "#/components/headers/Sunset"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "RateLimited": {
        "description": "JSON burst limit reached. Wait for Retry-After before retrying; quota is per IP, route and instance, not distributed.",
        "headers": {
          "API-Version": {
            "$ref": "#/components/headers/ApiVersion"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "Deprecation": {
            "$ref": "#/components/headers/Deprecation"
          },
          "Sunset": {
            "$ref": "#/components/headers/Sunset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "InvalidEventQuery": {
        "description": "Invalid JSON language, date, date range, integer, category/type selection, duplicate scalar or unknown parameter. Fix before retrying. Query values are not echoed.",
        "headers": {
          "API-Version": {
            "$ref": "#/components/headers/ApiVersion"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "Deprecation": {
            "$ref": "#/components/headers/Deprecation"
          },
          "Sunset": {
            "$ref": "#/components/headers/Sunset"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "CalendarUnavailable": {
        "description": "Calendar is unavailable; this is not an empty result. Wait 300 seconds before retrying.",
        "headers": {
          "API-Version": {
            "$ref": "#/components/headers/ApiVersion"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "Deprecation": {
            "$ref": "#/components/headers/Deprecation"
          },
          "Sunset": {
            "$ref": "#/components/headers/Sunset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    },
    "headers": {
      "ApiVersion": {
        "description": "Stable major API version.",
        "schema": {
          "type": "string",
          "enum": [
            "1"
          ]
        }
      },
      "RateLimitPolicy": {
        "description": "IETF draft-ietf-httpapi-ratelimit-headers-11 structured policy: 120 requests per 60 seconds per IP, route and server instance. Not a global quota. On JSON responses only.",
        "schema": {
          "type": "string",
          "example": "\"instance-read\";q=120;w=60"
        }
      },
      "RateLimit": {
        "description": "Current instance quota hint using IETF draft-ietf-httpapi-ratelimit-headers-11: r remaining requests, t whole seconds until reset. Counters reset on restart; new clients may share a conservative overflow quota at high client counts. Do not cache. On JSON responses only.",
        "schema": {
          "type": "string",
          "example": "\"instance-read\";r=119;t=60"
        }
      },
      "RetryAfter": {
        "description": "Whole seconds to wait before retrying. Takes precedence over RateLimit t.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "example": "60"
        }
      },
      "Deprecation": {
        "description": "Only sent when deprecation has been announced: RFC 9745 Structured Field Date (@ followed by Unix seconds). Currently absent; no retirement is scheduled.",
        "schema": {
          "type": "string",
          "pattern": "^@[0-9]+$",
          "example": "@1893456000"
        }
      },
      "Sunset": {
        "description": "Only sent on versions scheduled for retirement, at least 90 days after notice. RFC 8594 HTTP-date when the version may stop responding. Currently absent.",
        "schema": {
          "type": "string",
          "example": "Mon, 01 Apr 2030 00:00:00 GMT"
        }
      }
    }
  },
  "x-versioning-policy": {
    "url": "https://actsvejle.dk/docs#docs-versioning",
    "strategy": "URL major version",
    "current": "/api/v1",
    "minimumRetirementNoticeDays": 90
  }
}
