Australian public holidays, show days and school terms. One API, every state, every source cited.
Ask “is this date a holiday for this district?” and get the gazetted answer, including the regional show days and local replacements that state-level lists get wrong.
Get a free API key, or sign in
The link opens your account page. A new account gets its key there, shown once. An existing account sees its plan, Upgrade, Manage billing and Reset key. Signing in never changes your key; only Reset key does.
Try it
Examples that trip up other sources (annual plan, since they need a district): Karratha on King’s Birthday · Geelong on Melbourne Cup Day · Hobart on Recreation Day · Tasmania, Saturday 26 Dec
Endpoints
Base URL https://dates.gazetted.dev. Send the key as Authorization: Bearer gz_… or ?api_key=gz_…. All responses are JSON.
| Endpoint | Auth | What it returns |
|---|---|---|
GET /v1/is-holiday?state=&date=&q= | key | is_public_holiday, statewide records, regional matches for q, and any statewide holiday that is replaced in that district. q takes a postcode or a place name. |
GET /v1/holidays?state=&from=&to=&q=&type= | key | All holidays in the range. q takes a postcode, district, council or town. type is public_holiday, bank_holiday or government_holiday. |
GET /v1/business-days?state=&from=&to=&q= | key | How many working days fall between two dates, both included. Weekends and public holidays are taken out, for the district q names. |
GET /v1/business-days/add?state=&date=&days=&q= | key | The date days working days from date. Negative counts backwards. |
GET /v1/school-terms?state=&year= | key | Terms, holidays between terms, pupil-free days. Government schools. Most states run to 2030, NT and WA to 2032, Tasmania only to 2027. provisional: true marks a year the government has published but not settled. |
GET /v1/changes?since= | annual | Data changes since a date, by jurisdiction. Poll it, refetch what changed. |
GET /v1/me | key | Your plan and limits. |
POST /v1/checkout | key | Returns a Stripe Checkout URL for the annual plan. See Billing from code. |
POST /v1/billing/portal | key | Returns a Stripe billing page URL: receipts, card, cancel. See Billing from code. |
POST /v1/keys/rotate | key | New key, old one revoked. |
GET /v1/coverage | none | Which states and years exist. |
POST /v1/signup | none | {"email"} → emails a one-time link to your account page. A new account gets its key there. An existing key is left alone. Three per minute per address. |
Working days
Most calls to a holiday API are really one of two questions: how many working days are there between now and then, and what date is five working days out. Both need the holidays and the local replacement rules, and the replacements are the part that goes wrong. A Geelong employee never gets Melbourne Cup Day, so counting it as a day off makes every date after it wrong by one.
curl -H "Authorization: Bearer gz_…" \
"https://dates.gazetted.dev/v1/business-days?state=VIC&from=2026-11-01&to=2026-11-30"
{ "business_days": 20, "calendar_days": 30, "counted": "both endpoints included",
"holidays_excluded": [ { "date": "2026-11-03", "name": "Melbourne Cup Day", "scope": "state" } ] }
Add q=3220 and the same month comes back as 21: Greater Geelong swapped the Cup for a local day on 21 October, so November has one more working day there than it does in Melbourne.
curl -H "Authorization: Bearer gz_…" \
"https://dates.gazetted.dev/v1/business-days/add?state=QLD&date=2026-12-24&days=5"
{ "result": "2027-01-05",
"holidays_skipped": [ { "date": "2026-12-25", "name": "Christmas Day" },
{ "date": "2026-12-28", "name": "Boxing Day (additional public holiday)" },
{ "date": "2027-01-01", "name": "New Year's Day" } ],
"part_day_holidays": [ { "date": "2026-12-24", "name": "Christmas Eve", "part_day": "18:00-24:00" } ] }
The rules, so you can check them against your own:
- A working day is Monday to Friday and not a public holiday. There is no "next business day" rounding:
days=0hands back the date you gave, whatever day of the week it is, and counting starts the day after. - Bank holidays and government holidays are working days. Most people work them, so they are listed but not deducted.
- A part-day holiday is a working day. It comes back in
part_day_holidaysso you can decide. - A regional day only counts when the postcode match is certain. An uncertain one lands in
uncertain_matchesand changes no number, because quietly removing a working day nobody actually has off is the expensive mistake. - Both endpoints answer
404rather than guess if the answer falls in a year with no data. Check/v1/coveragefor the years we hold.
Free plan: statewide, current year, ranges up to 31 days. Regional working days and other years are on the annual plan.
Billing from code
Two ways to pay, same Stripe account behind both.
In a browser, sign in with your email and use the buttons on your account page. That is the normal way, and nothing below is needed.
From a script or a terminal, POST /v1/checkout and POST /v1/billing/portal do the same two things with your API key. Each returns a short-lived Stripe URL to open. Useful if you provision keys from your own tooling and never want to leave the shell.
curl -X POST -H "Authorization: Bearer gz_…" \
https://dates.gazetted.dev/v1/checkout
{ "url": "https://checkout.stripe.com/c/pay/cs_…" }
Open that URL to pay. Your plan flips to annual as soon as Stripe confirms the payment, usually a few seconds. /v1/checkout answers 409 if the account is already on the annual plan, and 503 while card payments are still switched off, which is the case today. /v1/billing/portal answers 404 until you have paid at least once, because there is no billing record to show yet.
Example
curl -H "Authorization: Bearer gz_…" \
"https://dates.gazetted.dev/v1/is-holiday?state=WA&date=2026-09-28&q=karratha"
{
"state": "WA",
"date": "2026-09-28",
"is_public_holiday": false,
"statewide": [ { "name": "King's Birthday", "type": "public_holiday", … } ],
"regional_matches": [],
"replaced_in_district": [ "King's Birthday" ]
}
Karratha holds its King’s Birthday on 3 August, so 28 September is a normal working day there. The response says so instead of guessing.
Record shape
| Field | Meaning |
|---|---|
date, name, jurisdiction | ISO date, name as published, state code. |
type | public_holiday, bank_holiday (the NSW and ACT August day, and Queensland's ~90 special holidays a year in the Torres Strait and Cape York: real, gazetted, but most people work them), government_holiday (Tasmania Easter Tuesday, Launceston Cup: public service only). Only public_holiday counts towards is_public_holiday. |
scope | state or regional. |
district_text | The government’s own wording for the area, verbatim. Some are prose (“south of and including Oatlands and Swansea”). |
precision | state, lga, postcodes, or sub_lga_prose when the district is a fragment of a council area described in words. |
replaces | Name of the statewide holiday this local one stands in for. On that statewide date, the district is not on holiday. |
part_day | e.g. 19:00-24:00 for SA and NT Christmas Eve. |
source_url, source_checked | Where it came from and when it was last checked. |
matched_by, match_basis, certain | Only when q was a postcode. See Postcode lookup. |
replaced_in_district | On a statewide row: this day is not observed here, a regional day stands in for it. |
Postcode lookup
Pass a four-digit postcode as q and regional days resolve without you knowing the district's name. Your payroll system has a postcode. It does not have "the area of the former Stanthorpe Shire".
curl -H "Authorization: Bearer gz_…" \
"https://dates.gazetted.dev/v1/is-holiday?state=VIC&date=2026-11-03&q=3220"
Melbourne Cup Day, postcode 3220. The answer is false: Geelong holds its own cup day instead, and replaced_in_district names the day that was given up.
Districts are written the way each government wrote them, so how far a postcode can be trusted varies. Every regional row matched by postcode says which case it is in match_basis, and certain is false for the last one.
match_basis | What it means |
|---|---|
gazetted | The government named the postcodes itself. Nothing was inferred. |
lga | The district is a whole council area. Exact, give or take where a postcode straddles a boundary. |
locality | The district is a list of named towns, and these are their postcodes. May reach slightly past the edge. |
parent | The district is a slice of something larger, described in words: a parish list, a former shire division, "all areas west of the Campaspe River". The postcode only says you are inside that larger area. certain is false, the day is left out of is_public_holiday, and no replacement is applied. Read district_text and decide. |
Postcodes come from the Australian Bureau of Statistics correspondence files, ASGS Edition 3, used under CC BY 4.0. 227 districts are covered. A place name still works if you prefer: q=karratha.
Caching
Holidays change a few times a year, when a state gazettes something. Don’t poll the data on a timer. Do this instead:
- Fetch what you need once and store it.
- Every data response carries
ETagandX-Data-Version. Re-request withIf-None-Matchand you get a304with no body when nothing has changed. - On the annual plan, poll
GET /v1/changes?since=<last date you saw>once a day. Whencountis above zero, refetch the jurisdictions it lists. That’s the whole refresh loop.
On the free plan, fetch the current year in 31-day windows and revalidate with If-None-Match. The version header tells you whether anything moved without downloading it again.
curl -i -H "Authorization: Bearer gz_…" -H 'If-None-Match: "<etag from last time>"' \
"https://dates.gazetted.dev/v1/holidays?state=QLD&from=2026-01-01&to=2026-12-31"
# HTTP/2 304 when unchanged
Plan and API changes
Any change to what the free plan includes, or to the API shape, is listed here dated, before it ships. Data changes go to the change feed.
| Date | Change |
|---|
Coverage
| State | Holidays | School terms |
|---|
Regional holidays: Queensland show days (98 districts), Tasmania (11 regional days with areas from the Statutory Holidays Act), Northern Territory show days, Victoria non-metropolitan replacements, Western Australia regional King’s Birthday. These fill in as states gazette them, usually September to November for the following year.
2026 and 2027 are checked by hand against each government page. 2020 to 2025 come from the federal dataset described below, which carries statewide holidays for all eight jurisdictions but almost no regional days. Every record says which source it came from. Ask for the current gap list at hello@gazetted.dev.
Why this exists
The federal machine-readable holidays dataset was retired in January 2026. State pages publish their own lists in eight different shapes, and none of them map regional days to a district you can query. Payroll, rostering and delivery apps end up hand-coding “second Monday in March” rules that drift the year a date is proclaimed differently.
Gazetted stores dates, not rules. Every record is a published date with the URL it came from. When a state changes something, the record changes and the source line tells you why.
Plans
Holidays don’t change often, so we don’t charge per request. The free plan is what the free lists already give you. The annual plan is everything they don’t.
| Free | Annual | |
|---|---|---|
| Statewide public holidays | current year | 2020 to 2027 |
| Regional and show days, local replacements | — | 2021 to 2027 |
| Date ranges | 31 days | unlimited |
| School terms | current term | all terms, all years |
| Change feed | — | /v1/changes |
| Licence | non-commercial | commercial |
| Price | $0 | A$99 a year, renews yearly, cancel any time |
Both plans have a per-minute cap for abuse control. Free-plan responses to regional or multi-year queries return 402 with a note, so you can see exactly where the line is.
Upgrade, manage billing, or reset your key
Sign in with your email at the top of this page for your account page: plan, billing and Reset key.
Card payments open shortly. Until then the annual plan is invoiced. Email hello@gazetted.dev with the address on your account and we'll send one.