API documentation · v1.0.0

Calendas API

Portuguese public holidays and business days, per municipality, with the source of every holiday.

Base URL
https://api.calendas.pt
Format
JSON (UTF-8), GET only
Specification
https://api.calendas.pt/openapi.json (OpenAPI 3.1)

Authentication

Every request needs an API key.

Send it in the Authorization header as a bearer token, or in X-Api-Key if your client cannot set Authorization. Never put it in the URL: URLs end up in logs. Keep the key in an environment variable or a secret store, not in your code.

Authorization: Bearer cal_…

Without a key, or with an unknown or revoked one, the API answers 401 with the code missing_api_key or invalid_api_key. A key is shown once, when it is created; to rotate it, ask for a new one and the old one is revoked.

The API is in a private beta: keys are given on request. Ask for access or write to [email protected].

Limits

Each key has a limit per minute and a daily quota.

A key can make 60 requests per minute (a sliding window of 60 seconds) and 1000 requests per day. The daily quota resets at midnight, Lisbon time; if you need more, write to us.

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 60
X-RateLimit-Quota-Limit: 1000
X-RateLimit-Quota-Remaining: 999
X-RateLimit-Quota-Reset: 43200

Every answer to a valid key carries both limits: X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset for the minute, and the same with Quota for the day. Reset is in seconds.

Every request with a valid key counts, also those that end in an error. Over either limit the API answers 429 with the code rate_limited and a Retry-After header with the seconds to wait; a refused request does not count against the quota.

First request

The holidays of a municipality in a year

The municipal holiday on 24 June, with the link to its source, the tier of that source and the date of the last verification. The answer below is real, shortened to two holidays.

Requestcurl

$ curl -G https://api.calendas.pt/v1/holidays \-H "Authorization: Bearer $CALENDAS_API_KEY" \-d year=2026 \-d municipality=guimaraes

Response200 · application/json

{  "data": {    "year": 2026,    "municipality": {      "ine_code": "0308",      "name": "Guimarães",      "slug": "guimaraes",      "district": "Braga",      "region": null    },    "holidays": [      {        "date": "2026-01-01",        "name": "Ano Novo",        "name_en": "New Year's Day",        "commemorates": null,        "commemorates_en": null,        "slug": "ano-novo",        "scope": "national",        "recurrence": "1 de janeiro",        "recurrence_en": "1 January",        "source_url": "https://diariodarepublica.pt/dr/legislacao-consolidada/lei/2009-34546475",        "source_kind": "legislacao",        "source_title": "Diário da República · Código do Trabalho (Lei n.º 7/2009), art. 234.º",        "source_tier": "A",        "verified_at": "2026-10-30",        "evidence": {          "confirmed_years": [],          "recurrence_confirmed": false        }      },      …,      {        "date": "2026-06-24",        "name": "Feriado municipal de Guimarães",        "name_en": "Guimarães municipal holiday",        "commemorates": "Batalha de São Mamede (1128)",        "commemorates_en": null,        "slug": "feriado-municipal-guimaraes",        "scope": "municipal",        "recurrence": "24 de junho",        "recurrence_en": "24 June",        "source_url": "https://www.visitguimaraes.travel/pages/1150/?geo_article_id=986",        "source_kind": "pagina_institucional",        "source_title": null,        "source_tier": "B",        "verified_at": "2026-11-01",        "evidence": {          "confirmed_years": [],          "recurrence_confirmed": false        }      }    ]  },  "warnings": []}

Conventions

One shape for every answer

  • A successful answer is {"data": …, "warnings": […]}. Read warnings: municipal_holiday_unknown means the municipal holiday of that year is not in the data, so the answer may miss a day off.
  • An error is {"error": {"code", "message", "parameter"}}, with parameter naming the parameter at fault. Branch on code; the message is for people and may change.
  • Dates are YYYY-MM-DD. Intervals include both ends.
  • A municipality is its slug (guimaraes) or its 4-digit INE code (0308); list them.
  • Holidays are never moved to Monday, and two holidays on the same day are both listed.
  • recurrence describes how a holiday recurs, for display; the date to use is date.

Endpoints

5 endpoints

GET /v1/municipalities

Every municipality (308), by name.

$ curl -G https://api.calendas.pt/v1/municipalities \-H "Authorization: Bearer $CALENDAS_API_KEY"
200
Municipalities.
401
No API key, or an unknown or revoked one (missing_api_key, invalid_api_key). The response carries WWW-Authenticate: Bearer.
429
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.
500
Error.

GET /v1/holidays

Every holiday of a municipality in a year.

All scopes (national, regional, municipal, optional), each with its source. Two holidays on the same day are both listed. Holidays never move to Monday.

ParameterTypeDescription
year required integer A year of the coverage (2000 to 2050).
municipality required string Municipality slug (e.g. guimaraes) or 4-digit INE code (e.g. 0308).
$ curl -G https://api.calendas.pt/v1/holidays \-H "Authorization: Bearer $CALENDAS_API_KEY" \-d year=2026 \-d municipality=guimaraes
200
Holidays of the year.
401
No API key, or an unknown or revoked one (missing_api_key, invalid_api_key). The response carries WWW-Authenticate: Bearer.
400
Error.
404
Error.
422
Error.
429
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.
500
Error.

GET /v1/holidays/check

The holidays on a day, and whether it is a holiday and a business day.

holidays lists every holiday on the day, whatever the flags; is_holiday and is_business_day follow municipal, carnival and rest_days.

ParameterTypeDescription
date required string (date)
municipality required string Municipality slug (e.g. guimaraes) or 4-digit INE code (e.g. 0308).
municipal boolean Whether the municipal holiday counts.
carnival boolean Whether Carnival (an optional holiday) counts.
rest_days string Comma-separated ISO weekdays (1 = Monday … 7 = Sunday). Present and empty: no rest days.
$ curl -G https://api.calendas.pt/v1/holidays/check \-H "Authorization: Bearer $CALENDAS_API_KEY" \-d date=2026-06-24 \-d municipality=guimaraes
200
The day.
401
No API key, or an unknown or revoked one (missing_api_key, invalid_api_key). The response carries WWW-Authenticate: Bearer.
400
Error.
404
Error.
422
Error.
429
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.
500
Error.

GET /v1/business-days/count

Business days between two days, both inclusive.

ParameterTypeDescription
from required string (date)
to required string (date) Same day as from or later.
municipality required string Municipality slug (e.g. guimaraes) or 4-digit INE code (e.g. 0308).
rest_days string Comma-separated ISO weekdays (1 = Monday … 7 = Sunday). Present and empty: no rest days.
municipal boolean Whether the municipal holiday counts.
carnival boolean Whether Carnival (an optional holiday) counts.
$ curl -G https://api.calendas.pt/v1/business-days/count \-H "Authorization: Bearer $CALENDAS_API_KEY" \-d from=2026-06-01 \-d to=2026-06-30 \-d municipality=guimaraes
200
Count.
401
No API key, or an unknown or revoked one (missing_api_key, invalid_api_key). The response carries WWW-Authenticate: Bearer.
400
Error.
404
Error.
422
Error.
429
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.
500
Error.

GET /v1/business-days/add

The day n business days after (or, with negative days, before) a day.

The start day does not count: days=0 returns the day itself when it is a business day, otherwise the next one.

ParameterTypeDescription
date required string (date)
days required integer
municipality required string Municipality slug (e.g. guimaraes) or 4-digit INE code (e.g. 0308).
rest_days string Comma-separated ISO weekdays (1 = Monday … 7 = Sunday). Present and empty: no rest days.
municipal boolean Whether the municipal holiday counts.
carnival boolean Whether Carnival (an optional holiday) counts.
$ curl -G https://api.calendas.pt/v1/business-days/add \-H "Authorization: Bearer $CALENDAS_API_KEY" \-d date=2026-06-24 \-d days=10 \-d municipality=guimaraes
200
Result.
401
No API key, or an unknown or revoked one (missing_api_key, invalid_api_key). The response carries WWW-Authenticate: Bearer.
400
Error.
404
Error.
422
Error.
429
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.
500
Error.

Sources and reliability

Every holiday says where it comes from

Each holiday carries the source it rests on and the tier of that source. Use the tier to decide how much to trust a date: for example, show tier C dates as provisional, or ask your users to confirm them.

The tier tells you what evidence we hold for a date, not whether the date is right. A tier C date comes from public sources and is regularly checked against other sources; it may well be right, but we have not yet confirmed it at an official source. B is already an official page stating the holiday; A is the published act itself.

Tier A · published act
The act that sets the holiday, as published: a law in the Diário da República, a deliberation or public notice of the municipality. verified_at is the day we last read it.
Tier B · official page
An official page of a public body (the municipality, a regional or central government service) that states the holiday, without the act itself.
Tier C · secondary source
An encyclopaedia, the press or an aggregator. Never counts as verified: verified_at is always null. We are replacing these with official sources.

The holiday object

FieldTypeDescription
date string (date)
name string Portuguese name, always. A municipal holiday is named after its municipality (Feriado municipal de Guimarães); what it commemorates is in commemorates.
name_en string | null English name, or null while there is no translation (never the Portuguese name).
commemorates string | null What the holiday commemorates, in Portuguese (e.g. Batalha de São Mamede (1128)), or null when the name already says it.
commemorates_en string | null commemorates in English, or null while there is no translation (never the Portuguese text).
slug string
scope string One of: national, regional, municipal, optional.
recurrence string How the holiday recurs, in Portuguese, for display. Use date: this is a description, not a formula.
recurrence_en string recurrence in English.
source_url string | null (uri) 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 string | null 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. One of: deliberacao, edital, pagina_institucional, noticia, agenda, legislacao, outra.
source_title string | null Short readable title of the source, for display.
source_tier string 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. One of: A, B, C.
verified_at string | null (date) Day the source was last checked; always null at tier C.
evidence object 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).

Errors

Error codes

StatusCodeMeaning
401 missing_api_key No API key was sent.
401 invalid_api_key The key is unknown, malformed or revoked.
400 invalid_parameter A parameter is missing, repeated or not valid; parameter names it.
404 municipality_not_found No municipality has that slug or INE code.
404 not_found No such endpoint.
422 out_of_coverage A date or year outside the years the API answers for.
429 rate_limited Over a limit of the key, per minute or per day; wait the seconds of Retry-After.
500 internal_error Our fault: it is logged and we are alerted.