{
  "openapi": "3.1.0",
  "info": {
    "title": "Kalenteri.online API",
    "version": "1.2.0",
    "description": "Suomen kalenterimerkinnät ja kuun vaiheet ohjelmalliseen käyttöön.\n\n**Maksuton käyttö**\n\n- Kuluvan vuoden rajatut kalenterimerkinnät (`id` ja `name`) sekä kuun vaiheet. Maksuton käyttö ei vaadi API-avainta.\n- Yksityinen ja kaupallinen käyttö sallittu lähdemaininnalla: Kalenteritiedot: [Kalenteri.online](https://kalenteri.online/).\n- Aineistoa ei saa jälleenmyydä sellaisenaan tai tarjota kilpailevana kalenteripalveluna tai kalenteri-API:na.\n\n**Kutsurajat ja välimuisti**\n\nMaksuttomilla kalenterihauilla on yhteinen kiintiö: **100 kutsua/IP/vuorokausi** ja **10 kutsua/IP/minuutti**. Vuorokausi vaihtuu keskiyöllä Suomen aikaa (`Europe/Helsinki`).\n\nHae koko vuosi ja säilytä tulos omassa välimuistissa. Päivitä enintään kerran vuorokaudessa ja noudata `Cache-Control`- ja `Expires`-otsakkeita. APIa ei saa kutsua jokaisella sivulatauksella.\n\n**Maksullinen käyttö**\n\nTilaukseen sisältyvät valittujen vuosien laajempi aineisto, Outlook-vienti, kaupallinen käyttölupa ilman pakollista lähdemainintaa ja API-käyttöoikeus 12 kuukaudeksi. Nimipäivät eivät sisälly aineistoon.\n\nKäytä avainta `Authorization: Bearer` -otsakkeessa. Virheellinen tai vanhentunut avain palauttaa virheen; pyyntö ei siirry maksuttomaan käyttöön.\n\n[Käyttöehdot](https://kalenteri.online/kayttoehdot#api-rajapinta)",
    "termsOfService": "https://kalenteri.online/kayttoehdot#api-rajapinta"
  },
  "servers": [
    {
      "url": "https://api.kalenteri.online/v1",
      "description": "Julkinen API"
    }
  ],
  "paths": {
    "/calendar/days": {
      "get": {
        "summary": "Hae kalenteripäivät",
        "description": "Palauttaa kalenterimerkinnät valitulta aikaväliltä. Ilman API-avainta saat kuluvan vuoden maksuttoman aineiston. API-avaimella aineisto ja vuodet määräytyvät tilauksesi mukaan.",
        "parameters": [
          {
            "$ref": "#/components/parameters/startDate"
          },
          {
            "$ref": "#/components/parameters/endDate"
          },
          {
            "$ref": "#/components/parameters/eventType"
          },
          {"$ref": "#/components/parameters/format"},
          {"$ref": "#/components/parameters/include"},
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "Aiemman maksuttoman vastauksen ETag. Lähetä vasta välimuistin voimassaoloajan päätyttyä.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarDaysResponse"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientPlan"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitExceeded"
          },
          "304": {
            "description": "Välimuistissa oleva maksuton vastaus vastaa If-None-Match-otsaketta. Ei vastausrunkoa. Myös tämä palvelimelle saapuva pyyntö kuluttaa kutsukiintiötä."
          }
        },
        "security": [
          {},
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Calendar"
        ]
      }
    },
    "/calendar/day/{date}": {
      "get": {
        "summary": "Hae yhden päivän kalenterimerkinnät",
        "description": "Palauttaa valitun päivän kalenterimerkinnät. Ilman API-avainta saat kuluvan vuoden maksuttoman aineiston. API-avaimella aineisto ja vuodet määräytyvät tilauksesi mukaan.",
        "parameters": [{"name": "date", "in": "path", "required": true, "schema": {"type": "string", "format": "date"}}, {"$ref": "#/components/parameters/format"}],
        "security": [{}, {"BearerAuth": []}],
        "tags": ["Calendar"],
        "responses": {
          "200": {"$ref": "#/components/responses/CalendarDaysResponse"},
          "400": {"$ref": "#/components/responses/BadRequest"},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "403": {"$ref": "#/components/responses/InsufficientPlan"},
          "429": {"$ref": "#/components/responses/RateLimitExceeded"}
        }
      }
    },
    "/calendar/search": {
      "get": {
        "summary": "Hae kalenterimerkintöjä nimellä",
        "description": "Hakee kalenterimerkintöjä nimen osalla. Haku ei erottele kirjainkokoa, ja aikaväli saa sisältää enintään 366 päivää. Ilman API-avainta haetaan kuluvan vuoden maksuttomasta aineistosta; API-avaimella tilauksesi mukaisesta aineistosta.",
        "parameters": [
          {"name": "q", "in": "query", "required": true, "description": "Nimen osa (2–100 merkkiä ympäröivien välilyöntien poistamisen jälkeen).", "schema": {"type": "string", "minLength": 2, "maxLength": 100}},
          {"$ref": "#/components/parameters/startDate"},
          {"$ref": "#/components/parameters/endDate"},
          {"$ref": "#/components/parameters/eventType"},
          {"$ref": "#/components/parameters/format"},
          {"$ref": "#/components/parameters/include"}
        ],
        "security": [{}, {"BearerAuth": []}],
        "tags": ["Calendar"],
        "responses": {
          "200": {"$ref": "#/components/responses/CalendarDaysResponse"},
          "400": {"$ref": "#/components/responses/BadRequest"},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "403": {"$ref": "#/components/responses/InsufficientPlan"},
          "429": {"$ref": "#/components/responses/RateLimitExceeded"}
        }
      }
    },
    "/calendar/moon-phases": {
      "get": {
        "summary": "Hae kuun vaiheet",
        "description": "Palauttaa kuun vaiheet valitulta aikaväliltä. Ilman API-avainta voit hakea kuluvan vuoden kuun vaiheet. API-avaimella haettavat vuodet määräytyvät tilauksesi mukaan.",
        "parameters": [
          {
            "$ref": "#/components/parameters/startDate"
          },
          {
            "$ref": "#/components/parameters/endDate"
          },
          {"$ref": "#/components/parameters/format"},
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "Aiemman maksuttoman vastauksen ETag. Lähetä vasta välimuistin voimassaoloajan päätyttyä.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MoonPhasesResponse"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientPlan"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitExceeded"
          },
          "304": {
            "description": "Välimuistissa oleva maksuton vastaus vastaa If-None-Match-otsaketta. Ei vastausrunkoa. Myös tämä palvelimelle saapuva pyyntö kuluttaa kutsukiintiötä."
          }
        },
        "security": [
          {},
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Calendar"
        ]
      }
    }
  },
  "components": {
    "parameters": {
      "include": {"name": "include", "in": "query", "description": "Pilkuilla erotellut lisävalinnat:\n\n- `metadata`: order-, slug-, shortName- ja abbrevation-kentät; vaatii maksullisen tilauksen.\n- `classes`: päiväluokat; vaatii laajan tilauksen.\n- `holidayObservances`: vappuaatto, juhannusaatto, jouluaatto ja uudenvuodenaatto; vain `type=publicHolidays`.\n- `holidaySundays`: sunnuntaipyhät; vain `type=publicHolidays`.\n\nEsimerkiksi `include=metadata,classes` tai `type=publicHolidays&include=holidayObservances,holidaySundays,metadata`. Ilman parametria lisätietoja ei palauteta. Aattojen vapaa tai lyhennetty työpäivä riippuu esimerkiksi työehtosopimuksesta.", "schema": {"type": "string", "examples": ["metadata", "classes", "metadata,classes", "holidayObservances", "holidayObservances,holidaySundays"]}},
      "format": {"name": "format", "in": "query", "description": "Vastauksen data-osan muoto. grouped (oletus) palauttaa päivämäärillä avainnetun objektin; list palauttaa listan, jonka jokaisella merkinnällä on date-kenttä. Tuntematon arvo palauttaa 400. Tyhjä tulos on vastaavasti {} tai []. Listan merkinnät ovat päivämäärän mukaisessa järjestyksessä; päivän sisäinen järjestys säilyy.", "schema": {"type": "string", "enum": ["grouped", "list"], "default": "grouped"}},
      "eventType": {
        "name": "type",
        "in": "query",
        "required": false,
        "description": "Valitsee näkymän tai rajauksen tilauksen sallimaan aineistoon:\n\n- `modern`: Moderni kalenteri.\n- `traditional`: Perinteinen kalenteri.\n- `publicHolidays`: viralliset pyhäpäivät.\n- `flagDays`: liputuspäivät.\n- `themeDays`: teemapäivät; vaatii laajan tilauksen.\n\nMuut valinnat ovat käytettävissä myös ilman API-avainta. Maksuttomalla tasolla moderni ja perinteinen näkymä rajataan maksuttomaan aineistoon; liputuspäivistä palautetaan vain viralliset ja vakiintuneet päivät. Tilaus määrää aineiston laajuuden. Ilman parametria palautetaan koko käytettävissä oleva aineisto. Kun type on valittu, aikaväli saa sisältää enintään 366 päivää.",
        "schema": {"type": "string", "enum": ["traditional", "modern", "publicHolidays", "flagDays", "themeDays"]}
      },
      "startDate": {
        "name": "startDate",
        "in": "query",
        "required": true,
        "description": "Alkupäivä muodossa YYYY-MM-DD. Ilman API-avainta vain kuluva vuosi; Bearer-avaimella tilaukseen kuuluvat vuodet.",
        "schema": {
          "type": "string",
          "format": "date"
        },
        "example": "2026-01-01"
      },
      "endDate": {
        "name": "endDate",
        "in": "query",
        "required": true,
        "description": "Loppupäivä muodossa YYYY-MM-DD. Päivä ei voi olla ennen alkupäivää. Ilman API-avainta vain kuluva vuosi; Bearer-avaimella tilaukseen kuuluvat vuodet.",
        "schema": {
          "type": "string",
          "format": "date"
        },
        "example": "2026-12-31"
      }

    },
    "responses": {
      "CalendarDaysResponse": {
        "description": "Kalenteripäivät, joiden sisältö määräytyy tilauksen ja valinnaisen type-valinnan mukaan. dateCount laskee data-osan päivämäärät.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/CalendarDaysResponse"
            },
            "example": {
              "generatedAt": "2026-01-01T00:00:00Z",
              "dateCount": 1,
              "data": {
                "2026-01-01": [
                  {
                    "id": 100000,
                    "name": "Uudenvuodenpäivä"
                  }
                ]
              },
              "source": {
                "name": "Kalenteri.online",
                "url": "https://kalenteri.online/"
              }
            }
          }
        },
        "headers": {
          "X-RateLimit-Limit-Day": {
            "description": "Kiintiön kutsuraja maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining-Day": {
            "description": "Kiintiössä jäljellä olevat kutsut maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset-Day": {
            "description": "Kiintiön nollautumisaika Unix-sekunteina maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Limit-Minute": {
            "description": "Kiintiön kutsuraja maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining-Minute": {
            "description": "Kiintiössä jäljellä olevat kutsut maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset-Minute": {
            "description": "Kiintiön nollautumisaika Unix-sekunteina maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "Cache-Control": {
            "description": "Maksuton: private, max-age keskiyöhön Suomen aikaa, enintään 86400 sekuntia. Maksullinen: no-store.",
            "schema": {
              "type": "string"
            }
          },
          "Expires": {
            "description": "Maksuttoman vastauksen välimuistin voimassaolon päättyminen HTTP-päivämääränä.",
            "schema": {
              "type": "string"
            }
          },
          "ETag": {
            "description": "Maksuttoman vastauksen sisällön tunniste.",
            "schema": {
              "type": "string"
            }
          }
        }
      },
      "MoonPhasesResponse": {
        "description": "Kuun vaiheet päivämäärän mukaan.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/MoonPhasesResponse"
            }
          }
        },
        "headers": {
          "X-RateLimit-Limit-Day": {
            "description": "Kiintiön kutsuraja maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining-Day": {
            "description": "Kiintiössä jäljellä olevat kutsut maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset-Day": {
            "description": "Kiintiön nollautumisaika Unix-sekunteina maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Limit-Minute": {
            "description": "Kiintiön kutsuraja maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining-Minute": {
            "description": "Kiintiössä jäljellä olevat kutsut maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset-Minute": {
            "description": "Kiintiön nollautumisaika Unix-sekunteina maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "Cache-Control": {
            "description": "Maksuton: private, max-age keskiyöhön Suomen aikaa, enintään 86400 sekuntia. Maksullinen: no-store.",
            "schema": {
              "type": "string"
            }
          },
          "Expires": {
            "description": "Maksuttoman vastauksen välimuistin voimassaolon päättyminen HTTP-päivämääränä.",
            "schema": {
              "type": "string"
            }
          },
          "ETag": {
            "description": "Maksuttoman vastauksen sisällön tunniste.",
            "schema": {
              "type": "string"
            }
          }
        }
      },
      "BadRequest": {
        "description": "Virheellinen päivämäärä, aikaväli tai tuntematon parametri.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "INVALID_DATE_RANGE",
                "message": "Invalid date range. 'startDate' must be before or equal to 'endDate'."
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Annettu Authorization-otsake on virheellinen tai API-avain on virheellinen, poistettu tai sen maksullinen käyttöoikeus ei ole voimassa. Maksuttomaan aineistoon ei siirrytä.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "INVALID_API_KEY",
                "message": "The API key is invalid or expired."
              }
            }
          }
        }
      },
      "InsufficientPlan": {
        "description": "Maksuton API kattaa vain kuluvan vuoden. Annetun avaimen tilaus ei kata pyydettyjä vuosia tai valinnainen parametri edellyttää laajempaa tilausta.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "INSUFFICIENT_PLAN",
                "message": "The requested data is not included in your API access permissions."
              }
            }
          }
        }
      },
      "RateLimitExceeded": {
        "description": "Kutsukiintiö ylittyi. Maksuttoman API:n kiintiö on 100 kutsua/IP/vuorokausi ja 10 kutsua/IP/minuutti, yhteinen päiville ja kuun vaiheille. Odota Retry-After-otsakkeen ilmoittama sekuntimäärä. Maksullisen API:n nykyiset rajat ovat 1 kutsu/5 sekuntia ja 10 kutsua/minuutti API-avainta ja IP-osoitetta kohti.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "RATE_LIMIT_EXCEEDED",
                "message": "The API request limit has been exceeded. Cache the data locally and try again later."
              }
            }
          }
        },
        "headers": {
          "X-RateLimit-Limit-Day": {
            "description": "Kiintiön kutsuraja maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining-Day": {
            "description": "Kiintiössä jäljellä olevat kutsut maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset-Day": {
            "description": "Kiintiön nollautumisaika Unix-sekunteina maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Limit-Minute": {
            "description": "Kiintiön kutsuraja maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining-Minute": {
            "description": "Kiintiössä jäljellä olevat kutsut maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset-Minute": {
            "description": "Kiintiön nollautumisaika Unix-sekunteina maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          },
          "Retry-After": {
            "description": "Odotusaika sekunteina maksuttomalla tasolla.",
            "schema": {
              "type": "integer"
            }
          }
        }
      }
    },
    "schemas": {
      "CalendarDaysResponse": {
        "type": "object",
        "required": [
          "generatedAt",
          "dateCount",
          "data"
        ],
        "properties": {
          "generatedAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "Z$",
            "description": "Vastauksen muodostamisaika UTC-ajassa Z-päätteellä. Välimuistista palautettu vastaus säilyttää muodostamisajan."
          },
          "dateCount": {
            "type": "integer",
            "description": "data-osan päivämäärien määrä."
          },
          "data": {
            "description": "format=grouped palauttaa päivämäärillä avainnetun objektin, format=list päivätyt merkinnät listana.",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "id",
                      "name"
                    ],
                    "properties": {
                      "id": {
                        "type": "integer",
                        "description": "Kalenterimerkinnän käsitteen pysyvä tunniste, ei yhden vuoden esiintymän tunniste tai järjestysnumero. Sama ID voi esiintyä eri päivämäärillä eri vuosina ja tarvittaessa samalla aikavälillä useita kertoja. Listamuodon date kertoo esiintymän päivämäärän; grouped-muodossa se on data-objektin avain."
                      },
                      "name": {
                        "type": "string"
                      },
                      "countries": {
                        "type": "string",
                        "description": "Merkintään liittyvä maa tai maat alkuperäisessä aineistomuodossa. Kenttä puuttuu, jos arvo on null. Sisältyy maksullisiin tilauksiin."
                      },
                      "traditional": {
                        "type": [
                          "string",
                          "integer"
                        ],
                        "description": "Suomalaisen kalenterin luokittelutieto. Traditional tarkoittaa suomalaisen virallisen kalenterin mukaista luokitusta. Arvo muodostuu yhdestä tai useammasta luokkanumerosta: 1 = pyhäpäivä, mutta ei kirkollinen, 2 = juhlapäivä Suomessa, 3 = tapahtuma tai ilmiö, 4 = muistopäivä, 5 = merkkipäivä, 6 = kirkollinen juhlapäivä ja 7 = kansanperinteinen päivä tai ajanjakso. Jos arvossa on mukana numero 9, päivä ei kuulu suomalaiseen viralliseen kalenteriin, vaan se piilotetaan perinteisestä kalenterista. Esimerkiksi 19 sisältää luokat 1 ja 9, joten päivä ei kuulu viralliseen kalenteriin. Arvoa pitää käsitellä numeroista muodostuvana luokituksena, ei tavallisena kokonaislukuna.",
                        "examples": [
                          5,
                          19
                        ]
                      },
                      "ecclesiastical": {
                        "type": [
                          "string",
                          "integer"
                        ],
                        "description": "Kirkollinen luokitus maksullisissa tilauksissa: 1 = kirkollinen juhlapäivä, 2 = kirkkovuoden juhlapäivä, 3 = kirkollinen erityispyhä, 4 = kirkollinen merkkipäivä. Numero 5 voi liittyä kirkolliseen teemapäivään. Arvo voi sisältää useamman luokkanumeron; käsittele se luokituksena, ei tavallisena kokonaislukuna. Kenttä puuttuu, jos arvo on null."
                      },
                      "flagDay": {
                        "type": "integer",
                        "description": "Maksullisen tilauksen liputuspäiväluokka."
                      },
                      "flag": {
                        "type": [
                          "string",
                          "integer"
                        ],
                        "description": "Maksullisen tilauksen lippulaji."
                      },
                      "publicHoliday": {
                        "type": "integer",
                        "description": "Maksullisen tilauksen arkipyhätieto."
                      },
                      "officialHoliday": {
                        "type": "integer",
                        "description": "Maksullisen tilauksen virallinen pyhäpäivätieto."
                      },
                      "themeDay": {
                        "type": [
                          "string",
                          "integer"
                        ],
                        "description": "Teemapäivän luokitus laajassa kalenteriaineistossa: 1 = epävirallinen teemapäivä, 2 = YK:n teemapäivä, 3 = YK-järjestön teemapäivä, 4 = virallinen teemapäivä, 5 = kirkollinen tai kampanjaluonteinen teemapäivä, 6 = harvinainen teemapäivä. Kenttä puuttuu muilta merkinnöiltä, jos arvo on null."
                      },
                      "classes": {
                        "type": "object",
                        "description": "Laajan kalenteriaineiston päiväluokat. Palautetaan vain, kun include=classes.",
                        "properties": {
                          "singular": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "plural": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        }
                      },
                      "order": {
                        "type": "integer",
                        "description": "Maksullisen aineiston metatieto, kun include=metadata. Puuttuu, jos aineiston arvo on null."
                      },
                      "slug": {
                        "type": "string",
                        "description": "Maksullisen aineiston metatieto, kun include=metadata. Puuttuu, jos aineiston arvo on null."
                      },
                      "shortName": {
                        "type": "string",
                        "description": "Maksullisen aineiston metatieto, kun include=metadata. Puuttuu, jos aineiston arvo on null."
                      },
                      "abbrevation": {
                        "type": "string",
                        "description": "Maksullisen aineiston metatieto, kun include=metadata. Puuttuu, jos aineiston arvo on null."
                      }
                    }
                  }
                }
              },
              {
                "type": "array",
                "items": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/CalendarDaysResponse/properties/data/oneOf/0/additionalProperties/items"
                    },
                    {
                      "type": "object",
                      "required": [
                        "date"
                      ],
                      "properties": {
                        "date": {
                          "type": "string",
                          "format": "date",
                          "description": "Merkinnän päivämäärä Suomen kalenterissa."
                        }
                      }
                    }
                  ]
                }
              }
            ]
          },
          "source": {
            "$ref": "#/components/schemas/Source"
          }
        }
      },
      "MoonPhasesResponse": {
        "type": "object",
        "required": [
          "generatedAt",
          "dateCount",
          "data"
        ],
        "properties": {
          "generatedAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "Z$",
            "description": "Vastauksen muodostamisaika UTC-ajassa Z-päätteellä. Välimuistista palautettu vastaus säilyttää muodostamisajan."
          },
          "dateCount": {
            "type": "integer",
            "description": "data-osan päivämäärien määrä."
          },
          "data": {
            "description": "format=grouped palauttaa päivämäärillä avainnetun objektin, format=list päivätyt kuun vaiheet listana.",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "moonPhase": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "moonPhase"
                    ]
                  }
                }
              },
              {
                "type": "array",
                "items": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/MoonPhasesResponse/properties/data/oneOf/0/additionalProperties/items"
                    },
                    {
                      "type": "object",
                      "required": [
                        "date"
                      ],
                      "properties": {
                        "date": {
                          "type": "string",
                          "format": "date"
                        }
                      }
                    }
                  ]
                }
              }
            ]
          },
          "source": {
            "$ref": "#/components/schemas/Source"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "INVALID_PARAMETER",
                  "INVALID_DATE",
                  "INVALID_DATE_RANGE",
                  "DATE_RANGE_TOO_LONG",
                  "INVALID_REQUEST",
                  "INVALID_API_KEY",
                  "INSUFFICIENT_PLAN",
                  "RATE_LIMIT_EXCEEDED",
                  "INTERNAL_ERROR"
                ],
                "description": "Vakaa koneellisesti käsiteltävä virhekoodi. Asiakkaan tulee sietää myös tulevaisuudessa lisättäviä koodeja."
              },
              "message": {
                "type": "string",
                "description": "Ihmiselle tarkoitettu selitys; sanamuoto voi muuttua. Älä päättele virhetyyppiä tästä tekstistä."
              }
            }
          }
        }
      },
      "Source": {
        "type": "object",
        "description": "Mukana kaikissa maksuttomissa onnistuneissa vastauksissa. Tietoja esitettäessä lähde on mainittava ja verkkoympäristössä linkitettävä.",
        "required": [
          "name",
          "url"
        ],
        "properties": {
          "name": {
            "type": "string",
            "const": "Kalenteri.online"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "const": "https://kalenteri.online/"
          }
        }
      }
    },
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Maksullinen API-avain Authorization: Bearer -otsakkeessa. Maksuttomassa käytössä jätä otsake kokonaan pois. Annettu avain ei koskaan siirry maksuttomaan aineistoon virhetilanteessa."
      }
    }
  },
  "externalDocs": {
    "description": "API:n esimerkit ja aineistojen vertailu",
    "url": "https://kalenteri.online/api-rajapinta"
  }
}
