{"openapi":"3.1.0","info":{"title":"Calendas API","version":"1.0.0","summary":"Portuguese public holidays and business days, per municipality, with the source of every holiday.","description":"Every successful response is `{\"data\": …, \"warnings\": […]}`; every error is `{\"error\": {\"code\", \"message\", \"parameter\"}}`. Dates are `YYYY-MM-DD` and intervals are inclusive. Each holiday carries its source: `source_url`, `source_kind` and `source_title` (what the document is, for display), `source_tier` (A: the published act; B: an official page without the act; C: a secondary source, never verified) and `verified_at`. Every request needs an API key, sent as `Authorization: Bearer <key>` (or in the `X-Api-Key` header), never in the query string; without one, or with an unknown or revoked one, the answer is 401 (`missing_api_key`, `invalid_api_key`). Each key has a limit per minute (60 requests) and a daily quota (1000 requests unless agreed otherwise, reset at midnight, Lisbon time); over either the answer is 429 (`rate_limited`) with `Retry-After`, and every answer to a valid key says both limits in its `X-RateLimit-*` headers. During the beta keys are given on request at https://calendas.pt.","contact":{"url":"https://calendas.pt"}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"servers":[{"url":"https://api.calendas.pt"}],"paths":{"/v1/municipalities":{"get":{"operationId":"listMunicipalities","summary":"Every municipality (308), by name.","responses":{"200":{"description":"Municipalities.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-RateLimit-Quota-Limit":{"$ref":"#/components/headers/QuotaLimit"},"X-RateLimit-Quota-Remaining":{"$ref":"#/components/headers/QuotaRemaining"},"X-RateLimit-Quota-Reset":{"$ref":"#/components/headers/QuotaReset"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Municipality"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/Error"}}}},"/v1/holidays":{"get":{"operationId":"listHolidays","summary":"Every holiday of a municipality in a year.","description":"All scopes (national, regional, municipal, optional), each with its source. Two holidays on the same day are both listed. Holidays never move to Monday.","parameters":[{"$ref":"#/components/parameters/Year"},{"$ref":"#/components/parameters/Municipality"}],"responses":{"200":{"description":"Holidays of the year.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-RateLimit-Quota-Limit":{"$ref":"#/components/headers/QuotaLimit"},"X-RateLimit-Quota-Remaining":{"$ref":"#/components/headers/QuotaRemaining"},"X-RateLimit-Quota-Reset":{"$ref":"#/components/headers/QuotaReset"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"data":{"type":"object","required":["year","municipality","holidays"],"properties":{"year":{"type":"integer"},"municipality":{"$ref":"#/components/schemas/Municipality"},"holidays":{"type":"array","items":{"$ref":"#/components/schemas/Holiday"}}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"400":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/Error"}}}},"/v1/holidays/check":{"get":{"operationId":"checkDate","summary":"The holidays on a day, and whether it is a holiday and a business day.","description":"`holidays` lists every holiday on the day, whatever the flags; `is_holiday` and `is_business_day` follow `municipal`, `carnival` and `rest_days`.","parameters":[{"$ref":"#/components/parameters/Date"},{"$ref":"#/components/parameters/Municipality"},{"$ref":"#/components/parameters/Municipal"},{"$ref":"#/components/parameters/Carnival"},{"$ref":"#/components/parameters/RestDays"}],"responses":{"200":{"description":"The day.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-RateLimit-Quota-Limit":{"$ref":"#/components/headers/QuotaLimit"},"X-RateLimit-Quota-Remaining":{"$ref":"#/components/headers/QuotaRemaining"},"X-RateLimit-Quota-Reset":{"$ref":"#/components/headers/QuotaReset"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"data":{"type":"object","required":["date","municipality","is_holiday","is_business_day","holidays"],"properties":{"date":{"type":"string","format":"date"},"municipality":{"$ref":"#/components/schemas/Municipality"},"is_holiday":{"type":"boolean"},"is_business_day":{"type":"boolean"},"holidays":{"type":"array","items":{"$ref":"#/components/schemas/Holiday"}}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"400":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/Error"}}}},"/v1/business-days/count":{"get":{"operationId":"countBusinessDays","summary":"Business days between two days, both inclusive.","parameters":[{"name":"from","in":"query","required":true,"schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":true,"description":"Same day as `from` or later.","schema":{"type":"string","format":"date"}},{"$ref":"#/components/parameters/Municipality"},{"$ref":"#/components/parameters/RestDays"},{"$ref":"#/components/parameters/Municipal"},{"$ref":"#/components/parameters/Carnival"}],"responses":{"200":{"description":"Count.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-RateLimit-Quota-Limit":{"$ref":"#/components/headers/QuotaLimit"},"X-RateLimit-Quota-Remaining":{"$ref":"#/components/headers/QuotaRemaining"},"X-RateLimit-Quota-Reset":{"$ref":"#/components/headers/QuotaReset"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"data":{"type":"object","required":["from","to","municipality","rest_days","municipal","carnival","business_days"],"properties":{"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"},"municipality":{"$ref":"#/components/schemas/Municipality"},"rest_days":{"$ref":"#/components/schemas/RestDays"},"municipal":{"type":"boolean"},"carnival":{"type":"boolean"},"business_days":{"type":"integer","minimum":0}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"400":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/Error"}}}},"/v1/business-days/add":{"get":{"operationId":"addBusinessDays","summary":"The day n business days after (or, with negative days, before) a day.","description":"The start day does not count: `days=0` returns the day itself when it is a business day, otherwise the next one.","parameters":[{"$ref":"#/components/parameters/Date"},{"name":"days","in":"query","required":true,"schema":{"type":"integer","minimum":-3650,"maximum":3650}},{"$ref":"#/components/parameters/Municipality"},{"$ref":"#/components/parameters/RestDays"},{"$ref":"#/components/parameters/Municipal"},{"$ref":"#/components/parameters/Carnival"}],"responses":{"200":{"description":"Result.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-RateLimit-Quota-Limit":{"$ref":"#/components/headers/QuotaLimit"},"X-RateLimit-Quota-Remaining":{"$ref":"#/components/headers/QuotaRemaining"},"X-RateLimit-Quota-Reset":{"$ref":"#/components/headers/QuotaReset"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"data":{"type":"object","required":["date","days","municipality","rest_days","municipal","carnival","result"],"properties":{"date":{"type":"string","format":"date"},"days":{"type":"integer"},"municipality":{"$ref":"#/components/schemas/Municipality"},"rest_days":{"$ref":"#/components/schemas/RestDays"},"municipal":{"type":"boolean"},"carnival":{"type":"boolean"},"result":{"type":"string","format":"date"}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"400":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/Error"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"The API key: `Authorization: Bearer cal_…`."},"apiKeyHeader":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"The same key, for clients that cannot set `Authorization`."}},"parameters":{"Municipality":{"name":"municipality","in":"query","required":true,"description":"Municipality slug (e.g. `guimaraes`) or 4-digit INE code (e.g. `0308`).","schema":{"type":"string"}},"Year":{"name":"year","in":"query","required":true,"description":"A year of the coverage (2000 to 2050).","schema":{"type":"integer","minimum":2000,"maximum":2050}},"Date":{"name":"date","in":"query","required":true,"schema":{"type":"string","format":"date"}},"Municipal":{"name":"municipal","in":"query","required":false,"description":"Whether the municipal holiday counts.","schema":{"type":"boolean","default":true}},"Carnival":{"name":"carnival","in":"query","required":false,"description":"Whether Carnival (an optional holiday) counts.","schema":{"type":"boolean","default":false}},"RestDays":{"name":"rest_days","in":"query","required":false,"description":"Comma-separated ISO weekdays (1 = Monday … 7 = Sunday). Present and empty: no rest days.","schema":{"type":"string","default":"6,7"}}},"schemas":{"Envelope":{"type":"object","required":["data","warnings"],"properties":{"data":[],"warnings":{"type":"array","description":"Warning codes, always a list. `municipal_holiday_unknown`: in a year of the answer the municipality has no municipal holiday in the data, so the answer leaves it out (it does not mean there is none). In /holidays/check and /business-days it only appears when `municipal` is true.","items":{"type":"string","enum":["municipal_holiday_unknown"]}}}},"Municipality":{"type":"object","required":["ine_code","name","slug","district","region"],"properties":{"ine_code":{"type":"string","pattern":"^[0-9]{4}$"},"name":{"type":"string"},"slug":{"type":"string"},"district":{"type":"string","description":"District, or island for the autonomous regions."},"region":{"type":["string","null"],"enum":["azores","madeira",null],"description":"Autonomous region, or null on the mainland."}}},"Holiday":{"type":"object","required":["date","name","name_en","commemorates","commemorates_en","slug","scope","recurrence","recurrence_en","source_url","source_kind","source_title","source_tier","verified_at","evidence"],"properties":{"date":{"type":"string","format":"date"},"name":{"type":"string","description":"Portuguese name, always. A municipal holiday is named after its municipality (`Feriado municipal de Guimarães`); what it commemorates is in `commemorates`."},"name_en":{"type":["string","null"],"description":"English name, or null while there is no translation (never the Portuguese name)."},"commemorates":{"type":["string","null"],"description":"What the holiday commemorates, in Portuguese (e.g. `Batalha de São Mamede (1128)`), or null when the name already says it.","examples":["São João Baptista"]},"commemorates_en":{"type":["string","null"],"description":"`commemorates` in English, or null while there is no translation (never the Portuguese text)."},"slug":{"type":"string"},"scope":{"type":"string","enum":["national","regional","municipal","optional"]},"recurrence":{"type":"string","description":"How the holiday recurs, in Portuguese, for display. Use `date`: this is a description, not a formula.","examples":["24 de junho","Quinta-feira, 60 dias depois do Domingo de Páscoa","Segunda-feira a seguir ao 1.º sábado de outubro"]},"recurrence_en":{"type":"string","description":"`recurrence` in English.","examples":["24 June","Thursday, 60 days after Easter Sunday","Monday after the first Saturday of October"]},"source_url":{"type":["string","null"],"format":"uri","description":"The official source (Diário da República for laws; the council's deliberation or notice for municipal holidays), or the secondary source at tier C."},"source_kind":{"type":["string","null"],"enum":["deliberacao","edital","pagina_institucional","noticia","agenda","legislacao","outra",null],"description":"Kind of document of the source: deliberation, public notice (edital), institutional page, news item, agenda event, legislation, or other (e.g. an encyclopaedia). It describes the document; its strength is `source_tier`."},"source_title":{"type":["string","null"],"description":"Short readable title of the source, for display.","examples":["Câmara Municipal de Guimarães · Comemorações do 24 de junho"]},"source_tier":{"type":"string","enum":["A","B","C"],"description":"A: the published act (law, decree, deliberation). B: an official page stating the holiday, without the act. C: a secondary source (press, Wikipedia), never verified."},"verified_at":{"type":["string","null"],"format":"date","description":"Day the source was last checked; always null at tier C."},"evidence":{"type":"object","required":["confirmed_years","recurrence_confirmed"],"description":"Evidences of use: official pages (a services notice, the programme of the municipal day) that say on which day the holiday was kept in a year. They confirm the use in those years, never the act: they do not change source_tier (A is the act only).","properties":{"confirmed_years":{"type":"array","items":{"type":"integer"},"description":"Years an official page confirms the holiday was kept on the day `recurrence` gives, oldest first. Empty without evidences.","examples":[[2023,2026]]},"recurrence_confirmed":{"type":"boolean","description":"True when one recurrence alone explains the dates of every confirmed year, and it is this holiday's `recurrence`. With a single year, or with years that several recurrences explain (e.g. the Monday after Pentecost and the last Monday of May in 2023 and 2026), false: the use is confirmed, the recurrence not yet."}}}}},"RestDays":{"type":"array","items":{"type":"integer","minimum":1,"maximum":7}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["invalid_parameter","not_found","municipality_not_found","out_of_coverage","internal_error"],"description":"400 invalid_parameter, 404 not_found / municipality_not_found, 422 out_of_coverage (a date outside the coverage), 500 internal_error."},"message":{"type":"string"},"parameter":{"type":"string","description":"The parameter at fault, when there is one."}}}}}},"headers":{"RateLimitLimit":{"description":"Requests allowed per minute to the key.","schema":{"type":"integer","example":60}},"RateLimitRemaining":{"description":"Requests left in the current minute (a sliding window of 60 seconds).","schema":{"type":"integer","example":59}},"RateLimitReset":{"description":"Seconds until a request of the minute is free again.","schema":{"type":"integer","example":60}},"QuotaLimit":{"description":"Requests allowed per day to the key.","schema":{"type":"integer","example":1000}},"QuotaRemaining":{"description":"Requests left today.","schema":{"type":"integer","example":999}},"QuotaReset":{"description":"Seconds until the daily quota resets (midnight, Lisbon time).","schema":{"type":"integer","example":43200}},"RetryAfter":{"description":"Seconds to wait before the next request.","schema":{"type":"integer","example":30}}},"responses":{"Unauthorized":{"description":"No API key, or an unknown or revoked one (`missing_api_key`, `invalid_api_key`). The response carries `WWW-Authenticate: Bearer`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"Falta a chave de API: envie-a no cabeçalho Authorization: Bearer <chave>. Acesso em https://calendas.pt; documentação em https://calendas.pt/en/docs."}}}}},"TooManyRequests":{"description":"Over a limit of the key (`rate_limited`): the requests per minute or the daily quota. Wait the seconds of `Retry-After`. A refused request does not count against the quota.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-RateLimit-Quota-Limit":{"$ref":"#/components/headers/QuotaLimit"},"X-RateLimit-Quota-Remaining":{"$ref":"#/components/headers/QuotaRemaining"},"X-RateLimit-Quota-Reset":{"$ref":"#/components/headers/QuotaReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Limite de pedidos por minuto ultrapassado (60 por minuto): tente de novo dentro de 12 s."}}}}},"Error":{"description":"Error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}