{
  "openapi": "3.1.0",
  "info": {
    "title": "PH Holidays API",
    "version": "1.0.0",
    "description": "Read-only JSON API for Philippine public holidays, with proclamation citations. Paid: calls need a Client key (calls without one are being phased out).\n\n- **CORS:** enabled for all origins.\n- **Caching:** responses are edge-cached for up to an hour, so cache on your side too.\n- **Dates:** `YYYY-MM-DD` in Asia/Manila.\n\n### Authentication\n\nSend a Client key (`phc_…`) in the `x-api-key` header, or as `Authorization: Bearer <key>`. Choose a Plan at `https://pholidays.com/pricing`, then create keys at `https://pholidays.com/account`.\n\nCalls without a key are being phased out. A call with a key is never affected.\n\n- `/v1/export`, `/v1/calendar.ics` and `/v1/changes` never served them: always `401` (`no_key`).\n- Other routes: served until a sunset date is announced.\n- Until that date: served with `Deprecation`, `Sunset` and `Link` headers.\n- From that date: `401` (`no_key`) with `detail.pricing`.\n\n### Plans\n\n- **Pro**: $19/month or $190/year, 50,000 requests a month, 120 a minute, 2 Client keys, email support within 2 business days. Includes: Core lookups (holidays, is-holiday, upcoming, long weekends, coverage); Workday calculations (/v1/workday); Local (locality) filters; Change feed; Links to the source proclamation.\n- **Business**: $59/month or $590/year, 500,000 requests a month, 600 a minute, 10 Client keys, email support within 1 business day. Includes: Core lookups (holidays, is-holiday, upcoming, long weekends, coverage); Workday calculations (/v1/workday); Local (locality) filters; Change feed; Links to the source proclamation; Bulk year export; iCal feed.\n\n**Business only:** Bulk year export; iCal feed.\n\n- Quotas are hard caps. Past the quota a call gets 429 with Retry-After. There is no overage billing.\n- Full refund on request within 14 days of a payment.\n- A `past_due` subscription keeps working for 7 days, then gets `403`; keys are never deleted and work again when payment succeeds.\n\n### Sandbox key\n\nA Customer with no Plan access right now (never subscribed, or their Plan has lapsed) can take a free **Sandbox key** at `https://pholidays.com/account`. No card. Use it to try the API before you buy.\n\n- It serves 2024 only, 100 requests a day (UTC day), and the core lookups only.\n- Refusals are not counted.\n\nPer route:\n\n- `/v1/holidays`: needs `year`, or `from` and `to`, inside 2024.\n- `/v1/holidays/{date}` and `/v1/is-holiday`: the date must be inside 2024.\n- `/v1/long-weekends`: needs `year` = 2024.\n- `/v1/upcoming`: always refused.\n- `/v1/workday`, `/v1/export`, `/v1/calendar.ics` and `/v1/changes`: Plan features, refused with `403 feature_not_in_plan`.\n- A `locality` other than `nationwide`, on a route that takes `locality`: a Plan feature, refused with `403 feature_not_in_plan`.\n- `/v1/coverage`: open to a Sandbox key.\n\nAn answer to a Sandbox key carries the header `x-pholidays-sandbox: 2024`:\n\n- On a `200` (a cache hit too), a `400`, a `404` from a lookup, the `403` refusals (`feature_not_in_plan`, `sandbox_year_only`) and the daily `429`.\n- Not on a `401`, the per-IP `429`, a `500`, the `OPTIONS` preflight or a `404` for an unknown path.\n- A `200` also has `meta.sandbox` (`year`, `requestsPerDay`, `remainingToday`, `note`) and `cache-control: private, no-store`.\n\nWhat a Sandbox key sees:\n\n- Every row keeps its `sourceUrl` and `proclamationNumber`. Provenance is part of every row. The source links listed in the Plans are the features built on them, not those fields.\n- `/v1/holidays?year=2024` keeps the local rows. The Plan feature is filtering by `locality`, not seeing local rows.\n- `/v1/coverage` returns counts, Source status and the verification summary, never holiday rows.\n\n### Errors and limits\n\nA refusal has the body `{ \"error\": \"...\", \"detail\": { \"reason\": \"...\" } }` and `cache-control: no-store`.\n\n- `401` `invalid_key`: the key is invalid or revoked. `no_key`: no key was sent. Always on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`; on every other route after an announced sunset date. It has `detail.pricing`.\n- `403` `past_due`, `subscription_inactive`, `feature_not_in_plan` or `sandbox_year_only`. `feature_not_in_plan` also has `detail.feature`, `detail.plan` (the cheapest Plan that has the feature) and `detail.pricing`, and its `error` names that Plan. `sandbox_year_only` also has `detail.year` and `detail.pricing`.\n- `429` `rate_limited` (`detail.limit` is `per_minute`) or `quota_exceeded` (`detail.limit` is `monthly_quota`, or `daily_quota` for a Sandbox key).\n- Every `429` has a `Retry-After` header in whole seconds.\n- A per-IP `429` has the body `{ \"error\": \"Rate limit exceeded. ...\" }` with no `detail`, and `Retry-After: 60`.\n\n### Data freshness and support\n\n- The sources (PCO and the Official Gazette) are checked once a day.\n- The annual PCO list is applied automatically.\n- Amendments, mid-year proclamations and some local proclamations need a manual review.\n- We add them within 2 business days of their publication by PCO or the Official Gazette.\n- Every answer carries meta.lastChecked, the last successful check of the sources (not a re-check of every holiday).\n- No uptime SLA.\n- Email support (support@pholidays.com) targets: Pro within 2 business days, Business within 1 business day.\n\n**Status values**\n\n- `confirmed` — declared by an official proclamation or statute.\n- `projected` — derived from fixed legal rules (fixed dates, Easter, last Monday of August); proclamation not yet seen.\n- `pending` — e.g. Eid'l Fitr/Adha: the date is an estimate until proclaimed.\n\nNationwide holidays are the focus. Local holidays are best-effort and always incomplete — see `/v1/coverage`.",
    "contact": {
      "name": "PH Holidays support",
      "email": "support@pholidays.com"
    }
  },
  "servers": [
    {
      "url": "https://api.pholidays.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "apiKey": []
    },
    {
      "bearer": []
    }
  ],
  "tags": [
    {
      "name": "Holidays",
      "description": "Look up holidays."
    },
    {
      "name": "Calendar",
      "description": "Derived views of the holiday calendar."
    },
    {
      "name": "Meta",
      "description": "Dataset freshness."
    }
  ],
  "paths": {
    "/v1/holidays": {
      "get": {
        "operationId": "listHolidays",
        "tags": [
          "Holidays"
        ],
        "summary": "List holidays",
        "parameters": [
          {
            "name": "year",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 2000,
              "maximum": 2100,
              "examples": [
                2026
              ]
            }
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/HolidayType"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/HolidayStatus"
            }
          },
          {
            "name": "locality",
            "in": "query",
            "description": "Locality slug, or `nationwide`.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "x-pholidays-feature": "local_filters"
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date",
              "examples": [
                "2026-12-25"
              ]
            },
            "description": "Inclusive start date.",
            "required": false
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date",
              "examples": [
                "2026-12-25"
              ]
            },
            "description": "Inclusive end date.",
            "required": false
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 366
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching holidays.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "Edge-cached; cache on your side too. A Sandbox key gets `private, no-store` instead."
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On every 200 to a Sandbox key, cache HIT included.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              },
              "Deprecation": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 9745: `@` followed by Unix seconds, 60 days before `Sunset`. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 8594 HTTP-date: when calls with no key start to get a `401`. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              },
              "Link": {
                "description": "Present only on a call with no key, once a sunset date is announced. `<https://pholidays.com/pricing>; rel=\"sunset\"`: where to get a key. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Holiday"
                      }
                    },
                    "meta": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Meta"
                        },
                        {
                          "type": "object",
                          "required": [
                            "count"
                          ],
                          "properties": {
                            "count": {
                              "type": "integer",
                              "description": "How many items `data` holds.",
                              "examples": [
                                1
                              ]
                            }
                          }
                        },
                        {
                          "type": "object",
                          "properties": {
                            "filters": {
                              "type": "object",
                              "description": "The parsed filters that were applied."
                            }
                          }
                        }
                      ]
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": 1,
                      "date": "2026-01-01",
                      "weekday": "Thursday",
                      "year": 2026,
                      "name": "New Year's Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 2,
                      "date": "2026-02-17",
                      "weekday": "Tuesday",
                      "year": 2026,
                      "name": "Chinese New Year",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/wp-content/uploads/2025/09/20250903-PROC-1006-FRM.pdf.pdf",
                      "isMovable": false,
                      "notes": null,
                      "source": "manual",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 3,
                      "date": "2026-02-25",
                      "weekday": "Wednesday",
                      "year": 2026,
                      "name": "EDSA People Power Revolution Anniversary",
                      "type": "special_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1,
                        "note": "Treated as an ordinary working day."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 4,
                      "date": "2026-03-20",
                      "weekday": "Friday",
                      "year": 2026,
                      "name": "Eid'l Fitr",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1189, s. 2026",
                      "sourceUrl": "https://pco.gov.ph/news_releases/proclamation-no-1189-s-2026-declaring-friday-20-march-2026-a-regular-holiday-throughout-the-country-in-observance-of-eidl-fitr-feast-of-ramadhan/",
                      "isMovable": true,
                      "notes": "Declared by Proclamation No. 1189, s. 2026 (Proclamation No. 1006, s. 2025 left the date to a later proclamation).",
                      "source": "manual",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 5,
                      "date": "2026-04-02",
                      "weekday": "Thursday",
                      "year": 2026,
                      "name": "Maundy Thursday",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 6,
                      "date": "2026-04-03",
                      "weekday": "Friday",
                      "year": 2026,
                      "name": "Good Friday",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 7,
                      "date": "2026-04-04",
                      "weekday": "Saturday",
                      "year": 2026,
                      "name": "Black Saturday",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 8,
                      "date": "2026-04-09",
                      "weekday": "Thursday",
                      "year": 2026,
                      "name": "Araw ng Kagitingan",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 9,
                      "date": "2026-05-01",
                      "weekday": "Friday",
                      "year": 2026,
                      "name": "Labor Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 10,
                      "date": "2026-05-27",
                      "weekday": "Wednesday",
                      "year": 2026,
                      "name": "Eid'l Adha",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1264, s. 2026",
                      "sourceUrl": "https://pco.gov.ph/news_releases/proclamation-no-1264-s-2026/",
                      "isMovable": true,
                      "notes": "Declared by Proclamation No. 1264, s. 2026 (Proclamation No. 1006, s. 2025 left the date to a later proclamation).",
                      "source": "manual",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 11,
                      "date": "2026-06-12",
                      "weekday": "Friday",
                      "year": 2026,
                      "name": "Independence Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 12,
                      "date": "2026-08-21",
                      "weekday": "Friday",
                      "year": 2026,
                      "name": "Ninoy Aquino Day",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 13,
                      "date": "2026-08-31",
                      "weekday": "Monday",
                      "year": 2026,
                      "name": "National Heroes Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 14,
                      "date": "2026-11-01",
                      "weekday": "Sunday",
                      "year": 2026,
                      "name": "All Saints' Day",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 15,
                      "date": "2026-11-02",
                      "weekday": "Monday",
                      "year": 2026,
                      "name": "All Souls' Day",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 16,
                      "date": "2026-11-30",
                      "weekday": "Monday",
                      "year": 2026,
                      "name": "Bonifacio Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 17,
                      "date": "2026-12-08",
                      "weekday": "Tuesday",
                      "year": 2026,
                      "name": "Feast of the Immaculate Conception of Mary",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 18,
                      "date": "2026-12-24",
                      "weekday": "Thursday",
                      "year": 2026,
                      "name": "Christmas Eve",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/wp-content/uploads/2025/09/20250903-PROC-1006-FRM.pdf.pdf",
                      "isMovable": false,
                      "notes": null,
                      "source": "manual",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 19,
                      "date": "2026-12-25",
                      "weekday": "Friday",
                      "year": 2026,
                      "name": "Christmas Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 20,
                      "date": "2026-12-30",
                      "weekday": "Wednesday",
                      "year": 2026,
                      "name": "Rizal Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 21,
                      "date": "2026-12-31",
                      "weekday": "Thursday",
                      "year": 2026,
                      "name": "Last Day of the Year",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    }
                  ],
                  "meta": {
                    "lastUpdated": "2026-01-15T08:30:00.000Z",
                    "lastChecked": "2026-12-25T02:00:00.000Z",
                    "coverage": "Nationwide holidays are tracked per annual proclamation. Years without a proclamation are rule-based projections (status \"projected\"). Eid dates stay \"pending\" until proclaimed. Local holidays are best-effort and incomplete.",
                    "officialSource": "https://www.officialgazette.gov.ph/nationwide-holidays/",
                    "count": 21,
                    "filters": {
                      "year": 2026,
                      "limit": 366
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters; `detail` holds the field errors. A Sandbox key gets the same `400`, marked.",
            "headers": {
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On a 400 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              },
              "Deprecation": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 9745: `@` followed by Unix seconds, 60 days before `Sunset`. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 8594 HTTP-date: when calls with no key start to get a `401`. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              },
              "Link": {
                "description": "Present only on a call with no key, once a sunset date is announced. `<https://pholidays.com/pricing>; rel=\"sunset\"`: where to get a key. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid query parameters",
                  "detail": {
                    "year": [
                      "Invalid input"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "The key is invalid or revoked, or none was sent. `detail.reason` is `invalid_key` or `no_key` (with `detail.pricing`). A call with no key always gets `no_key` on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, which never served one. On the other routes it gets `no_key` only once an announced sunset date has passed; until then it is served, with `Deprecation`, `Sunset` and `Link` headers. Never marked with the Sandbox header.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "invalid_key": {
                    "summary": "The key is unknown, revoked or expired",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "invalid_key"
                      }
                    }
                  },
                  "no_key": {
                    "summary": "No key: always on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, on every other route after an announced sunset date",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "no_key",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Refused. `detail.reason` is `sandbox_year_only` (a Sandbox key asked outside 2024; `detail.year` and `detail.pricing` say what a Sandbox key serves and where to buy a Plan), `feature_not_in_plan` (a Sandbox key sent a `locality` other than `nationwide`; `detail.feature`, `detail.plan` and `detail.pricing` say which feature is missing, the cheapest Plan that has it and where to buy it), `past_due` (payment failed and the 7-day grace is over) or `subscription_inactive` (no active subscription).",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On the Sandbox refusals (`sandbox_year_only`, `feature_not_in_plan`).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "sandbox_year_only": {
                    "summary": "A Sandbox key asked outside 2024",
                    "value": {
                      "error": "Sandbox keys serve 2024 only. See https://pholidays.com/pricing for a Plan",
                      "detail": {
                        "reason": "sandbox_year_only",
                        "year": 2024,
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  },
                  "feature_not_in_plan": {
                    "summary": "A Sandbox key sent a `locality` other than `nationwide`",
                    "value": {
                      "error": "Not included with this key: Local (locality) filters. The Pro Plan includes it. See https://pholidays.com/pricing",
                      "detail": {
                        "reason": "feature_not_in_plan",
                        "feature": "local_filters",
                        "plan": "pro",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  },
                  "past_due": {
                    "summary": "Payment failed and the 7-day grace is over",
                    "value": {
                      "error": "Subscription inactive, update payment",
                      "detail": {
                        "reason": "past_due"
                      }
                    }
                  },
                  "subscription_inactive": {
                    "summary": "No active subscription",
                    "value": {
                      "error": "Subscription inactive",
                      "detail": {
                        "reason": "subscription_inactive"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over a limit; wait `Retry-After` seconds. For a Client key `detail.limit` is `per_minute` (reason `rate_limited`, 60 seconds) or `monthly_quota` (reason `quota_exceeded`, until the next month); for a Sandbox key it is `daily_quota` (until the next UTC day). A per-IP limit returns `{ error }` with no `detail` and 60 seconds.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "examples": [
                    60
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. Only on the `daily_quota` 429 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Over the per-minute limit",
                    "value": {
                      "error": "Per-minute rate limit exceeded",
                      "detail": {
                        "reason": "rate_limited",
                        "limit": "per_minute"
                      }
                    }
                  },
                  "quota_exceeded": {
                    "summary": "Over the monthly quota (for a Sandbox key, the daily quota)",
                    "value": {
                      "error": "Monthly quota exceeded",
                      "detail": {
                        "reason": "quota_exceeded",
                        "limit": "monthly_quota"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error. Never marked with the Sandbox header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Internal error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/holidays/{date}": {
      "get": {
        "operationId": "getHolidaysOnDate",
        "tags": [
          "Holidays"
        ],
        "summary": "Holidays on a date",
        "description": "Everything recorded on a date: nationwide holidays plus those of `locality`.",
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "schema": {
              "type": "string",
              "format": "date",
              "examples": [
                "2026-12-25"
              ]
            },
            "description": "Date to look up.",
            "required": true
          },
          {
            "name": "locality",
            "in": "query",
            "description": "Free-text locality slug, e.g. `talisay-batangas`. Adds that locality to the nationwide holidays; when omitted, only nationwide holidays are returned.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "x-pholidays-feature": "local_filters"
          }
        ],
        "responses": {
          "200": {
            "description": "Holidays on that date.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "Edge-cached; cache on your side too. A Sandbox key gets `private, no-store` instead."
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On every 200 to a Sandbox key, cache HIT included.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              },
              "Deprecation": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 9745: `@` followed by Unix seconds, 60 days before `Sunset`. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 8594 HTTP-date: when calls with no key start to get a `401`. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              },
              "Link": {
                "description": "Present only on a call with no key, once a sunset date is announced. `<https://pholidays.com/pricing>; rel=\"sunset\"`: where to get a key. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Holiday"
                      }
                    },
                    "meta": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Meta"
                        },
                        {
                          "type": "object",
                          "required": [
                            "count"
                          ],
                          "properties": {
                            "count": {
                              "type": "integer",
                              "description": "How many items `data` holds.",
                              "examples": [
                                1
                              ]
                            }
                          }
                        }
                      ]
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": 19,
                      "date": "2026-12-25",
                      "weekday": "Friday",
                      "year": 2026,
                      "name": "Christmas Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    }
                  ],
                  "meta": {
                    "lastUpdated": "2026-01-15T08:30:00.000Z",
                    "lastChecked": "2026-12-25T02:00:00.000Z",
                    "coverage": "Nationwide holidays are tracked per annual proclamation. Years without a proclamation are rule-based projections (status \"projected\"). Eid dates stay \"pending\" until proclaimed. Local holidays are best-effort and incomplete.",
                    "officialSource": "https://www.officialgazette.gov.ph/nationwide-holidays/",
                    "count": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters; `detail` holds the field errors. A malformed path date is also a `400`, with `error` \"Invalid date, expected YYYY-MM-DD\" and no `detail`. A Sandbox key gets the same `400`, marked.",
            "headers": {
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On a 400 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              },
              "Deprecation": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 9745: `@` followed by Unix seconds, 60 days before `Sunset`. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 8594 HTTP-date: when calls with no key start to get a `401`. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              },
              "Link": {
                "description": "Present only on a call with no key, once a sunset date is announced. `<https://pholidays.com/pricing>; rel=\"sunset\"`: where to get a key. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid query parameters",
                  "detail": {
                    "locality": [
                      "Too small: expected string to have >=1 characters"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "The key is invalid or revoked, or none was sent. `detail.reason` is `invalid_key` or `no_key` (with `detail.pricing`). A call with no key always gets `no_key` on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, which never served one. On the other routes it gets `no_key` only once an announced sunset date has passed; until then it is served, with `Deprecation`, `Sunset` and `Link` headers. Never marked with the Sandbox header.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "invalid_key": {
                    "summary": "The key is unknown, revoked or expired",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "invalid_key"
                      }
                    }
                  },
                  "no_key": {
                    "summary": "No key: always on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, on every other route after an announced sunset date",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "no_key",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Refused. `detail.reason` is `sandbox_year_only` (a Sandbox key asked outside 2024; `detail.year` and `detail.pricing` say what a Sandbox key serves and where to buy a Plan), `feature_not_in_plan` (a Sandbox key sent a `locality` other than `nationwide`; `detail.feature`, `detail.plan` and `detail.pricing` say which feature is missing, the cheapest Plan that has it and where to buy it), `past_due` (payment failed and the 7-day grace is over) or `subscription_inactive` (no active subscription).",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On the Sandbox refusals (`sandbox_year_only`, `feature_not_in_plan`).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "sandbox_year_only": {
                    "summary": "A Sandbox key asked outside 2024",
                    "value": {
                      "error": "Sandbox keys serve 2024 only. See https://pholidays.com/pricing for a Plan",
                      "detail": {
                        "reason": "sandbox_year_only",
                        "year": 2024,
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  },
                  "feature_not_in_plan": {
                    "summary": "A Sandbox key sent a `locality` other than `nationwide`",
                    "value": {
                      "error": "Not included with this key: Local (locality) filters. The Pro Plan includes it. See https://pholidays.com/pricing",
                      "detail": {
                        "reason": "feature_not_in_plan",
                        "feature": "local_filters",
                        "plan": "pro",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  },
                  "past_due": {
                    "summary": "Payment failed and the 7-day grace is over",
                    "value": {
                      "error": "Subscription inactive, update payment",
                      "detail": {
                        "reason": "past_due"
                      }
                    }
                  },
                  "subscription_inactive": {
                    "summary": "No active subscription",
                    "value": {
                      "error": "Subscription inactive",
                      "detail": {
                        "reason": "subscription_inactive"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No holiday recorded on that date. A Sandbox key gets the same `404`, marked.",
            "headers": {
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On a 404 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              },
              "Deprecation": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 9745: `@` followed by Unix seconds, 60 days before `Sunset`. On a 404 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 8594 HTTP-date: when calls with no key start to get a `401`. On a 404 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              },
              "Link": {
                "description": "Present only on a call with no key, once a sunset date is announced. `<https://pholidays.com/pricing>; rel=\"sunset\"`: where to get a key. On a 404 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No holiday recorded on 2026-12-25"
                }
              }
            }
          },
          "429": {
            "description": "Over a limit; wait `Retry-After` seconds. For a Client key `detail.limit` is `per_minute` (reason `rate_limited`, 60 seconds) or `monthly_quota` (reason `quota_exceeded`, until the next month); for a Sandbox key it is `daily_quota` (until the next UTC day). A per-IP limit returns `{ error }` with no `detail` and 60 seconds.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "examples": [
                    60
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. Only on the `daily_quota` 429 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Over the per-minute limit",
                    "value": {
                      "error": "Per-minute rate limit exceeded",
                      "detail": {
                        "reason": "rate_limited",
                        "limit": "per_minute"
                      }
                    }
                  },
                  "quota_exceeded": {
                    "summary": "Over the monthly quota (for a Sandbox key, the daily quota)",
                    "value": {
                      "error": "Monthly quota exceeded",
                      "detail": {
                        "reason": "quota_exceeded",
                        "limit": "monthly_quota"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error. Never marked with the Sandbox header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Internal error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/is-holiday": {
      "get": {
        "operationId": "isHoliday",
        "tags": [
          "Holidays"
        ],
        "summary": "Is this date a holiday?",
        "description": "Boolean answer plus the holidays behind it. Pending (unproclaimed) ones are reported separately. Special *working* days are listed but are ordinary working days.",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date",
              "examples": [
                "2026-12-25"
              ]
            },
            "description": "Date to check.",
            "required": true
          },
          {
            "name": "locality",
            "in": "query",
            "description": "Free-text locality slug, e.g. `talisay-batangas`. Adds that locality to the nationwide holidays; when omitted, only nationwide holidays are returned.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "x-pholidays-feature": "local_filters"
          }
        ],
        "responses": {
          "200": {
            "description": "Answer for the date.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "Edge-cached; cache on your side too. A Sandbox key gets `private, no-store` instead."
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On every 200 to a Sandbox key, cache HIT included.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              },
              "Deprecation": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 9745: `@` followed by Unix seconds, 60 days before `Sunset`. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 8594 HTTP-date: when calls with no key start to get a `401`. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              },
              "Link": {
                "description": "Present only on a call with no key, once a sunset date is announced. `<https://pholidays.com/pricing>; rel=\"sunset\"`: where to get a key. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "date",
                        "locality",
                        "isHoliday",
                        "basis",
                        "holidays",
                        "pending"
                      ],
                      "properties": {
                        "date": {
                          "type": "string",
                          "format": "date"
                        },
                        "locality": {
                          "type": "string",
                          "examples": [
                            "nationwide"
                          ]
                        },
                        "isHoliday": {
                          "type": "boolean"
                        },
                        "basis": {
                          "type": "string",
                          "enum": [
                            "none",
                            "confirmed",
                            "projected"
                          ],
                          "description": "`projected` = rule-derived, proclamation not yet seen. Derived from non-working holidays only, so `none` whenever `isHoliday` is false."
                        },
                        "holidays": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Holiday"
                          }
                        },
                        "pending": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Holiday"
                          }
                        }
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                },
                "example": {
                  "data": {
                    "date": "2026-12-25",
                    "locality": "nationwide",
                    "isHoliday": true,
                    "basis": "confirmed",
                    "holidays": [
                      {
                        "id": 19,
                        "date": "2026-12-25",
                        "weekday": "Friday",
                        "year": 2026,
                        "name": "Christmas Day",
                        "type": "regular",
                        "status": "confirmed",
                        "locality": "nationwide",
                        "region": null,
                        "province": null,
                        "proclamationNumber": "1006, s. 2025",
                        "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                        "isMovable": false,
                        "notes": null,
                        "source": "scraper",
                        "premiumPay": {
                          "unworked": 1,
                          "worked": 2,
                          "note": "Paid even if unworked; 200% of daily wage if worked."
                        },
                        "updatedAt": "2026-01-15T08:30:00.000Z"
                      }
                    ],
                    "pending": []
                  },
                  "meta": {
                    "lastUpdated": "2026-01-15T08:30:00.000Z",
                    "lastChecked": "2026-12-25T02:00:00.000Z",
                    "coverage": "Nationwide holidays are tracked per annual proclamation. Years without a proclamation are rule-based projections (status \"projected\"). Eid dates stay \"pending\" until proclaimed. Local holidays are best-effort and incomplete.",
                    "officialSource": "https://www.officialgazette.gov.ph/nationwide-holidays/"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters; `detail` holds the field errors. A Sandbox key gets the same `400`, marked.",
            "headers": {
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On a 400 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              },
              "Deprecation": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 9745: `@` followed by Unix seconds, 60 days before `Sunset`. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 8594 HTTP-date: when calls with no key start to get a `401`. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              },
              "Link": {
                "description": "Present only on a call with no key, once a sunset date is announced. `<https://pholidays.com/pricing>; rel=\"sunset\"`: where to get a key. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid query parameters",
                  "detail": {
                    "date": [
                      "Expected a valid YYYY-MM-DD date"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "The key is invalid or revoked, or none was sent. `detail.reason` is `invalid_key` or `no_key` (with `detail.pricing`). A call with no key always gets `no_key` on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, which never served one. On the other routes it gets `no_key` only once an announced sunset date has passed; until then it is served, with `Deprecation`, `Sunset` and `Link` headers. Never marked with the Sandbox header.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "invalid_key": {
                    "summary": "The key is unknown, revoked or expired",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "invalid_key"
                      }
                    }
                  },
                  "no_key": {
                    "summary": "No key: always on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, on every other route after an announced sunset date",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "no_key",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Refused. `detail.reason` is `sandbox_year_only` (a Sandbox key asked outside 2024; `detail.year` and `detail.pricing` say what a Sandbox key serves and where to buy a Plan), `feature_not_in_plan` (a Sandbox key sent a `locality` other than `nationwide`; `detail.feature`, `detail.plan` and `detail.pricing` say which feature is missing, the cheapest Plan that has it and where to buy it), `past_due` (payment failed and the 7-day grace is over) or `subscription_inactive` (no active subscription).",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On the Sandbox refusals (`sandbox_year_only`, `feature_not_in_plan`).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "sandbox_year_only": {
                    "summary": "A Sandbox key asked outside 2024",
                    "value": {
                      "error": "Sandbox keys serve 2024 only. See https://pholidays.com/pricing for a Plan",
                      "detail": {
                        "reason": "sandbox_year_only",
                        "year": 2024,
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  },
                  "feature_not_in_plan": {
                    "summary": "A Sandbox key sent a `locality` other than `nationwide`",
                    "value": {
                      "error": "Not included with this key: Local (locality) filters. The Pro Plan includes it. See https://pholidays.com/pricing",
                      "detail": {
                        "reason": "feature_not_in_plan",
                        "feature": "local_filters",
                        "plan": "pro",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  },
                  "past_due": {
                    "summary": "Payment failed and the 7-day grace is over",
                    "value": {
                      "error": "Subscription inactive, update payment",
                      "detail": {
                        "reason": "past_due"
                      }
                    }
                  },
                  "subscription_inactive": {
                    "summary": "No active subscription",
                    "value": {
                      "error": "Subscription inactive",
                      "detail": {
                        "reason": "subscription_inactive"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over a limit; wait `Retry-After` seconds. For a Client key `detail.limit` is `per_minute` (reason `rate_limited`, 60 seconds) or `monthly_quota` (reason `quota_exceeded`, until the next month); for a Sandbox key it is `daily_quota` (until the next UTC day). A per-IP limit returns `{ error }` with no `detail` and 60 seconds.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "examples": [
                    60
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. Only on the `daily_quota` 429 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Over the per-minute limit",
                    "value": {
                      "error": "Per-minute rate limit exceeded",
                      "detail": {
                        "reason": "rate_limited",
                        "limit": "per_minute"
                      }
                    }
                  },
                  "quota_exceeded": {
                    "summary": "Over the monthly quota (for a Sandbox key, the daily quota)",
                    "value": {
                      "error": "Monthly quota exceeded",
                      "detail": {
                        "reason": "quota_exceeded",
                        "limit": "monthly_quota"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error. Never marked with the Sandbox header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Internal error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/upcoming": {
      "get": {
        "operationId": "upcomingHolidays",
        "tags": [
          "Holidays"
        ],
        "summary": "Upcoming holidays",
        "description": "Next holidays from today (Asia/Manila), or from `from`.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 10
            }
          },
          {
            "name": "locality",
            "in": "query",
            "description": "Free-text locality slug, e.g. `talisay-batangas`. Adds that locality to the nationwide holidays; when omitted, only nationwide holidays are returned.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "x-pholidays-feature": "local_filters"
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date",
              "examples": [
                "2026-12-25"
              ]
            },
            "description": "Start date; defaults to today in Asia/Manila.",
            "required": false
          }
        ],
        "responses": {
          "200": {
            "description": "Upcoming holidays.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "Edge-cached; cache on your side too. A Sandbox key gets `private, no-store` instead."
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On every 200 to a Sandbox key, cache HIT included.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              },
              "Deprecation": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 9745: `@` followed by Unix seconds, 60 days before `Sunset`. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 8594 HTTP-date: when calls with no key start to get a `401`. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              },
              "Link": {
                "description": "Present only on a call with no key, once a sunset date is announced. `<https://pholidays.com/pricing>; rel=\"sunset\"`: where to get a key. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Holiday"
                      }
                    },
                    "meta": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Meta"
                        },
                        {
                          "type": "object",
                          "required": [
                            "count"
                          ],
                          "properties": {
                            "count": {
                              "type": "integer",
                              "description": "How many items `data` holds.",
                              "examples": [
                                1
                              ]
                            }
                          }
                        }
                      ]
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": 19,
                      "date": "2026-12-25",
                      "weekday": "Friday",
                      "year": 2026,
                      "name": "Christmas Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 20,
                      "date": "2026-12-30",
                      "weekday": "Wednesday",
                      "year": 2026,
                      "name": "Rizal Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 21,
                      "date": "2026-12-31",
                      "weekday": "Thursday",
                      "year": 2026,
                      "name": "Last Day of the Year",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 22,
                      "date": "2027-01-01",
                      "weekday": "Friday",
                      "year": 2027,
                      "name": "New Year's Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1427, s. 2026",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-declares-2027-regular-and-special-holidays/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 23,
                      "date": "2027-02-06",
                      "weekday": "Saturday",
                      "year": 2027,
                      "name": "Chinese New Year",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1427, s. 2026",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-declares-2027-regular-and-special-holidays/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 24,
                      "date": "2027-02-25",
                      "weekday": "Thursday",
                      "year": 2027,
                      "name": "EDSA People Power Revolution Anniversary",
                      "type": "special_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1427, s. 2026",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-declares-2027-regular-and-special-holidays/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1,
                        "note": "Treated as an ordinary working day."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 25,
                      "date": "2027-03-10",
                      "weekday": "Wednesday",
                      "year": 2027,
                      "name": "Eid'l Fitr",
                      "type": "regular",
                      "status": "pending",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": null,
                      "sourceUrl": null,
                      "isMovable": true,
                      "notes": "Date is an astronomical estimate; set by proclamation after moon sighting.",
                      "source": "projection",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 26,
                      "date": "2027-03-25",
                      "weekday": "Thursday",
                      "year": 2027,
                      "name": "Maundy Thursday",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1427, s. 2026",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-declares-2027-regular-and-special-holidays/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 27,
                      "date": "2027-03-26",
                      "weekday": "Friday",
                      "year": 2027,
                      "name": "Good Friday",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1427, s. 2026",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-declares-2027-regular-and-special-holidays/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 28,
                      "date": "2027-03-27",
                      "weekday": "Saturday",
                      "year": 2027,
                      "name": "Black Saturday",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1427, s. 2026",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-declares-2027-regular-and-special-holidays/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    }
                  ],
                  "meta": {
                    "lastUpdated": "2026-01-15T08:30:00.000Z",
                    "lastChecked": "2026-12-25T02:00:00.000Z",
                    "coverage": "Nationwide holidays are tracked per annual proclamation. Years without a proclamation are rule-based projections (status \"projected\"). Eid dates stay \"pending\" until proclaimed. Local holidays are best-effort and incomplete.",
                    "officialSource": "https://www.officialgazette.gov.ph/nationwide-holidays/",
                    "count": 10
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters; `detail` holds the field errors. A Sandbox key gets the same `400`, marked.",
            "headers": {
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On a 400 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              },
              "Deprecation": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 9745: `@` followed by Unix seconds, 60 days before `Sunset`. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 8594 HTTP-date: when calls with no key start to get a `401`. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              },
              "Link": {
                "description": "Present only on a call with no key, once a sunset date is announced. `<https://pholidays.com/pricing>; rel=\"sunset\"`: where to get a key. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid query parameters",
                  "detail": {
                    "limit": [
                      "Too small: expected number to be >=1"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "The key is invalid or revoked, or none was sent. `detail.reason` is `invalid_key` or `no_key` (with `detail.pricing`). A call with no key always gets `no_key` on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, which never served one. On the other routes it gets `no_key` only once an announced sunset date has passed; until then it is served, with `Deprecation`, `Sunset` and `Link` headers. Never marked with the Sandbox header.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "invalid_key": {
                    "summary": "The key is unknown, revoked or expired",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "invalid_key"
                      }
                    }
                  },
                  "no_key": {
                    "summary": "No key: always on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, on every other route after an announced sunset date",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "no_key",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Refused. `detail.reason` is `sandbox_year_only` (always, for a Sandbox key without a non-nationwide `locality`: it looks forward from today; `detail.year` and `detail.pricing` say what a Sandbox key serves and where to buy a Plan), `feature_not_in_plan` (a Sandbox key sent a `locality` other than `nationwide`; `detail.feature`, `detail.plan` and `detail.pricing` say which feature is missing, the cheapest Plan that has it and where to buy it), `past_due` (payment failed and the 7-day grace is over) or `subscription_inactive` (no active subscription).",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On the Sandbox refusals (`sandbox_year_only`, `feature_not_in_plan`).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "sandbox_year_only": {
                    "summary": "Always, for a Sandbox key without a non-nationwide `locality`: it looks forward from today",
                    "value": {
                      "error": "Sandbox keys serve 2024 only. See https://pholidays.com/pricing for a Plan",
                      "detail": {
                        "reason": "sandbox_year_only",
                        "year": 2024,
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  },
                  "feature_not_in_plan": {
                    "summary": "A Sandbox key sent a `locality` other than `nationwide`",
                    "value": {
                      "error": "Not included with this key: Local (locality) filters. The Pro Plan includes it. See https://pholidays.com/pricing",
                      "detail": {
                        "reason": "feature_not_in_plan",
                        "feature": "local_filters",
                        "plan": "pro",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  },
                  "past_due": {
                    "summary": "Payment failed and the 7-day grace is over",
                    "value": {
                      "error": "Subscription inactive, update payment",
                      "detail": {
                        "reason": "past_due"
                      }
                    }
                  },
                  "subscription_inactive": {
                    "summary": "No active subscription",
                    "value": {
                      "error": "Subscription inactive",
                      "detail": {
                        "reason": "subscription_inactive"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over a limit; wait `Retry-After` seconds. For a Client key `detail.limit` is `per_minute` (reason `rate_limited`, 60 seconds) or `monthly_quota` (reason `quota_exceeded`, until the next month); for a Sandbox key it is `daily_quota` (until the next UTC day). A per-IP limit returns `{ error }` with no `detail` and 60 seconds.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "examples": [
                    60
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. Only on the `daily_quota` 429 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Over the per-minute limit",
                    "value": {
                      "error": "Per-minute rate limit exceeded",
                      "detail": {
                        "reason": "rate_limited",
                        "limit": "per_minute"
                      }
                    }
                  },
                  "quota_exceeded": {
                    "summary": "Over the monthly quota (for a Sandbox key, the daily quota)",
                    "value": {
                      "error": "Monthly quota exceeded",
                      "detail": {
                        "reason": "quota_exceeded",
                        "limit": "monthly_quota"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error. Never marked with the Sandbox header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Internal error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workday": {
      "get": {
        "operationId": "workday",
        "x-pholidays-feature": "workday",
        "tags": [
          "Calendar"
        ],
        "summary": "Premium pay for a date",
        "description": "Labor Code (Art. 91–94) / DOLE premium-pay multipliers for the first eight hours. Informational only — company policy or a CBA may be better.",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date",
              "examples": [
                "2026-12-25"
              ]
            },
            "description": "Date to compute.",
            "required": true
          },
          {
            "name": "locality",
            "in": "query",
            "description": "Free-text locality slug, e.g. `talisay-batangas`. Adds that locality to the nationwide holidays; when omitted, only nationwide holidays are returned.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "x-pholidays-feature": "local_filters"
          },
          {
            "name": "restDay",
            "in": "query",
            "description": "Employee's weekly rest day, 0 = Sunday … 6 = Saturday.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 6,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Premium-pay breakdown.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "Edge-cached; cache on your side too. A Sandbox key gets `private, no-store` instead."
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On every 200 to a Sandbox key, cache HIT included.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              },
              "Deprecation": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 9745: `@` followed by Unix seconds, 60 days before `Sunset`. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 8594 HTTP-date: when calls with no key start to get a `401`. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              },
              "Link": {
                "description": "Present only on a call with no key, once a sunset date is announced. `<https://pholidays.com/pricing>; rel=\"sunset\"`: where to get a key. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "date",
                        "weekday",
                        "isRestDay",
                        "holidays",
                        "pendingHolidays",
                        "isOrdinaryWorkday",
                        "ifUnworked",
                        "ifWorked",
                        "basis",
                        "basisStatus"
                      ],
                      "properties": {
                        "date": {
                          "type": "string",
                          "format": "date"
                        },
                        "weekday": {
                          "type": "string",
                          "examples": [
                            "Friday"
                          ]
                        },
                        "isRestDay": {
                          "type": "boolean"
                        },
                        "holidays": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Holiday"
                          },
                          "description": "Holidays that count (confirmed or projected)."
                        },
                        "pendingHolidays": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Holiday"
                          },
                          "description": "Not yet proclaimed; excluded from the calculation."
                        },
                        "isOrdinaryWorkday": {
                          "type": "boolean"
                        },
                        "ifUnworked": {
                          "type": "number",
                          "description": "Pay if not worked, as a multiple of daily wage."
                        },
                        "ifWorked": {
                          "type": "number",
                          "description": "Pay for the first 8 hours if worked, as a multiple of daily wage."
                        },
                        "basis": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Plain-language reasons for the rate."
                        },
                        "basisStatus": {
                          "type": "string",
                          "enum": [
                            "confirmed",
                            "projected",
                            "none"
                          ],
                          "description": "From non-working holidays only: `none` when the day is not a holiday (including a special working day)."
                        }
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                },
                "example": {
                  "data": {
                    "date": "2026-12-25",
                    "weekday": "Friday",
                    "isRestDay": false,
                    "holidays": [
                      {
                        "id": 19,
                        "date": "2026-12-25",
                        "weekday": "Friday",
                        "year": 2026,
                        "name": "Christmas Day",
                        "type": "regular",
                        "status": "confirmed",
                        "locality": "nationwide",
                        "region": null,
                        "province": null,
                        "proclamationNumber": "1006, s. 2025",
                        "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                        "isMovable": false,
                        "notes": null,
                        "source": "scraper",
                        "premiumPay": {
                          "unworked": 1,
                          "worked": 2,
                          "note": "Paid even if unworked; 200% of daily wage if worked."
                        },
                        "updatedAt": "2026-01-15T08:30:00.000Z"
                      }
                    ],
                    "pendingHolidays": [],
                    "isOrdinaryWorkday": false,
                    "ifUnworked": 1,
                    "ifWorked": 2,
                    "basis": [
                      "Regular holiday"
                    ],
                    "basisStatus": "confirmed"
                  },
                  "meta": {
                    "lastUpdated": "2026-01-15T08:30:00.000Z",
                    "lastChecked": "2026-12-25T02:00:00.000Z",
                    "coverage": "Nationwide holidays are tracked per annual proclamation. Years without a proclamation are rule-based projections (status \"projected\"). Eid dates stay \"pending\" until proclaimed. Local holidays are best-effort and incomplete.",
                    "officialSource": "https://www.officialgazette.gov.ph/nationwide-holidays/"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters; `detail` holds the field errors. A Sandbox key gets the same `400`, marked.",
            "headers": {
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On a 400 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              },
              "Deprecation": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 9745: `@` followed by Unix seconds, 60 days before `Sunset`. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 8594 HTTP-date: when calls with no key start to get a `401`. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              },
              "Link": {
                "description": "Present only on a call with no key, once a sunset date is announced. `<https://pholidays.com/pricing>; rel=\"sunset\"`: where to get a key. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid query parameters",
                  "detail": {
                    "date": [
                      "Expected a valid YYYY-MM-DD date"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "The key is invalid or revoked, or none was sent. `detail.reason` is `invalid_key` or `no_key` (with `detail.pricing`). A call with no key always gets `no_key` on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, which never served one. On the other routes it gets `no_key` only once an announced sunset date has passed; until then it is served, with `Deprecation`, `Sunset` and `Link` headers. Never marked with the Sandbox header.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "invalid_key": {
                    "summary": "The key is unknown, revoked or expired",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "invalid_key"
                      }
                    }
                  },
                  "no_key": {
                    "summary": "No key: always on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, on every other route after an announced sunset date",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "no_key",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Refused. `detail.reason` is `feature_not_in_plan` (the key lacks the `workday` feature; always for a Sandbox key; `detail.feature`, `detail.plan` and `detail.pricing` say which feature is missing, the cheapest Plan that has it and where to buy it), `past_due` (payment failed and the 7-day grace is over) or `subscription_inactive` (no active subscription).",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On the Sandbox refusals (`feature_not_in_plan`).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "feature_not_in_plan": {
                    "summary": "The key lacks the `workday` feature; always for a Sandbox key",
                    "value": {
                      "error": "Not included with this key: Workday calculations (/v1/workday). The Pro Plan includes it. See https://pholidays.com/pricing",
                      "detail": {
                        "reason": "feature_not_in_plan",
                        "feature": "workday",
                        "plan": "pro",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  },
                  "past_due": {
                    "summary": "Payment failed and the 7-day grace is over",
                    "value": {
                      "error": "Subscription inactive, update payment",
                      "detail": {
                        "reason": "past_due"
                      }
                    }
                  },
                  "subscription_inactive": {
                    "summary": "No active subscription",
                    "value": {
                      "error": "Subscription inactive",
                      "detail": {
                        "reason": "subscription_inactive"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over a limit; wait `Retry-After` seconds. For a Client key `detail.limit` is `per_minute` (reason `rate_limited`, 60 seconds) or `monthly_quota` (reason `quota_exceeded`, until the next month); for a Sandbox key it is `daily_quota` (until the next UTC day). A per-IP limit returns `{ error }` with no `detail` and 60 seconds.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "examples": [
                    60
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. Only on the `daily_quota` 429 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Over the per-minute limit",
                    "value": {
                      "error": "Per-minute rate limit exceeded",
                      "detail": {
                        "reason": "rate_limited",
                        "limit": "per_minute"
                      }
                    }
                  },
                  "quota_exceeded": {
                    "summary": "Over the monthly quota (for a Sandbox key, the daily quota)",
                    "value": {
                      "error": "Monthly quota exceeded",
                      "detail": {
                        "reason": "quota_exceeded",
                        "limit": "monthly_quota"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error. Never marked with the Sandbox header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Internal error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/long-weekends": {
      "get": {
        "operationId": "longWeekends",
        "tags": [
          "Calendar"
        ],
        "summary": "Long weekends",
        "description": "Runs of 3+ consecutive non-working days that include a holiday. Nationwide, non-pending, non-working holidays only (special working days do not count).",
        "parameters": [
          {
            "name": "year",
            "in": "query",
            "description": "Defaults to the current year in Asia/Manila.",
            "schema": {
              "type": "integer",
              "minimum": 2000,
              "maximum": 2100,
              "examples": [
                2026
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Long weekends in the year.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "Edge-cached; cache on your side too. A Sandbox key gets `private, no-store` instead."
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On every 200 to a Sandbox key, cache HIT included.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              },
              "Deprecation": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 9745: `@` followed by Unix seconds, 60 days before `Sunset`. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 8594 HTTP-date: when calls with no key start to get a `401`. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              },
              "Link": {
                "description": "Present only on a call with no key, once a sunset date is announced. `<https://pholidays.com/pricing>; rel=\"sunset\"`: where to get a key. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LongWeekend"
                      }
                    },
                    "meta": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Meta"
                        },
                        {
                          "type": "object",
                          "required": [
                            "count"
                          ],
                          "properties": {
                            "count": {
                              "type": "integer",
                              "description": "How many items `data` holds.",
                              "examples": [
                                1
                              ]
                            }
                          }
                        },
                        {
                          "type": "object",
                          "properties": {
                            "year": {
                              "type": "integer"
                            },
                            "note": {
                              "type": "string"
                            }
                          }
                        }
                      ]
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "start": "2026-03-20",
                      "end": "2026-03-22",
                      "days": 3,
                      "label": "Mar 20 – Mar 22",
                      "holidays": [
                        {
                          "date": "2026-03-20",
                          "name": "Eid'l Fitr",
                          "type": "regular",
                          "status": "confirmed"
                        }
                      ]
                    },
                    {
                      "start": "2026-04-02",
                      "end": "2026-04-05",
                      "days": 4,
                      "label": "Apr 2 – Apr 5",
                      "holidays": [
                        {
                          "date": "2026-04-02",
                          "name": "Maundy Thursday",
                          "type": "regular",
                          "status": "confirmed"
                        },
                        {
                          "date": "2026-04-03",
                          "name": "Good Friday",
                          "type": "regular",
                          "status": "confirmed"
                        },
                        {
                          "date": "2026-04-04",
                          "name": "Black Saturday",
                          "type": "special_non_working",
                          "status": "confirmed"
                        }
                      ]
                    },
                    {
                      "start": "2026-05-01",
                      "end": "2026-05-03",
                      "days": 3,
                      "label": "May 1 – May 3",
                      "holidays": [
                        {
                          "date": "2026-05-01",
                          "name": "Labor Day",
                          "type": "regular",
                          "status": "confirmed"
                        }
                      ]
                    },
                    {
                      "start": "2026-06-12",
                      "end": "2026-06-14",
                      "days": 3,
                      "label": "Jun 12 – Jun 14",
                      "holidays": [
                        {
                          "date": "2026-06-12",
                          "name": "Independence Day",
                          "type": "regular",
                          "status": "confirmed"
                        }
                      ]
                    },
                    {
                      "start": "2026-08-21",
                      "end": "2026-08-23",
                      "days": 3,
                      "label": "Aug 21 – Aug 23",
                      "holidays": [
                        {
                          "date": "2026-08-21",
                          "name": "Ninoy Aquino Day",
                          "type": "special_non_working",
                          "status": "confirmed"
                        }
                      ]
                    },
                    {
                      "start": "2026-08-29",
                      "end": "2026-08-31",
                      "days": 3,
                      "label": "Aug 29 – Aug 31",
                      "holidays": [
                        {
                          "date": "2026-08-31",
                          "name": "National Heroes Day",
                          "type": "regular",
                          "status": "confirmed"
                        }
                      ]
                    },
                    {
                      "start": "2026-10-31",
                      "end": "2026-11-02",
                      "days": 3,
                      "label": "Oct 31 – Nov 2",
                      "holidays": [
                        {
                          "date": "2026-11-01",
                          "name": "All Saints' Day",
                          "type": "special_non_working",
                          "status": "confirmed"
                        },
                        {
                          "date": "2026-11-02",
                          "name": "All Souls' Day",
                          "type": "special_non_working",
                          "status": "confirmed"
                        }
                      ]
                    },
                    {
                      "start": "2026-11-28",
                      "end": "2026-11-30",
                      "days": 3,
                      "label": "Nov 28 – Nov 30",
                      "holidays": [
                        {
                          "date": "2026-11-30",
                          "name": "Bonifacio Day",
                          "type": "regular",
                          "status": "confirmed"
                        }
                      ]
                    },
                    {
                      "start": "2026-12-24",
                      "end": "2026-12-27",
                      "days": 4,
                      "label": "Dec 24 – Dec 27",
                      "holidays": [
                        {
                          "date": "2026-12-24",
                          "name": "Christmas Eve",
                          "type": "special_non_working",
                          "status": "confirmed"
                        },
                        {
                          "date": "2026-12-25",
                          "name": "Christmas Day",
                          "type": "regular",
                          "status": "confirmed"
                        }
                      ]
                    }
                  ],
                  "meta": {
                    "lastUpdated": "2026-01-15T08:30:00.000Z",
                    "lastChecked": "2026-12-25T02:00:00.000Z",
                    "coverage": "Nationwide holidays are tracked per annual proclamation. Years without a proclamation are rule-based projections (status \"projected\"). Eid dates stay \"pending\" until proclaimed. Local holidays are best-effort and incomplete.",
                    "officialSource": "https://www.officialgazette.gov.ph/nationwide-holidays/",
                    "count": 9,
                    "year": 2026,
                    "note": "Nationwide, non-pending non-working holidays only; pending (Eid) excluded."
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters; `detail` holds the field errors. A Sandbox key gets the same `400`, marked.",
            "headers": {
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On a 400 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              },
              "Deprecation": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 9745: `@` followed by Unix seconds, 60 days before `Sunset`. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 8594 HTTP-date: when calls with no key start to get a `401`. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              },
              "Link": {
                "description": "Present only on a call with no key, once a sunset date is announced. `<https://pholidays.com/pricing>; rel=\"sunset\"`: where to get a key. On a 400 to a call with no key.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid query parameters",
                  "detail": {
                    "year": [
                      "Invalid input"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "The key is invalid or revoked, or none was sent. `detail.reason` is `invalid_key` or `no_key` (with `detail.pricing`). A call with no key always gets `no_key` on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, which never served one. On the other routes it gets `no_key` only once an announced sunset date has passed; until then it is served, with `Deprecation`, `Sunset` and `Link` headers. Never marked with the Sandbox header.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "invalid_key": {
                    "summary": "The key is unknown, revoked or expired",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "invalid_key"
                      }
                    }
                  },
                  "no_key": {
                    "summary": "No key: always on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, on every other route after an announced sunset date",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "no_key",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Refused. `detail.reason` is `sandbox_year_only` (a Sandbox key sent no `year` or one outside 2024; `detail.year` and `detail.pricing` say what a Sandbox key serves and where to buy a Plan), `past_due` (payment failed and the 7-day grace is over) or `subscription_inactive` (no active subscription).",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On the Sandbox refusals (`sandbox_year_only`).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "sandbox_year_only": {
                    "summary": "A Sandbox key sent no `year` or one outside 2024",
                    "value": {
                      "error": "Sandbox keys serve 2024 only. See https://pholidays.com/pricing for a Plan",
                      "detail": {
                        "reason": "sandbox_year_only",
                        "year": 2024,
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  },
                  "past_due": {
                    "summary": "Payment failed and the 7-day grace is over",
                    "value": {
                      "error": "Subscription inactive, update payment",
                      "detail": {
                        "reason": "past_due"
                      }
                    }
                  },
                  "subscription_inactive": {
                    "summary": "No active subscription",
                    "value": {
                      "error": "Subscription inactive",
                      "detail": {
                        "reason": "subscription_inactive"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over a limit; wait `Retry-After` seconds. For a Client key `detail.limit` is `per_minute` (reason `rate_limited`, 60 seconds) or `monthly_quota` (reason `quota_exceeded`, until the next month); for a Sandbox key it is `daily_quota` (until the next UTC day). A per-IP limit returns `{ error }` with no `detail` and 60 seconds.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "examples": [
                    60
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. Only on the `daily_quota` 429 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Over the per-minute limit",
                    "value": {
                      "error": "Per-minute rate limit exceeded",
                      "detail": {
                        "reason": "rate_limited",
                        "limit": "per_minute"
                      }
                    }
                  },
                  "quota_exceeded": {
                    "summary": "Over the monthly quota (for a Sandbox key, the daily quota)",
                    "value": {
                      "error": "Monthly quota exceeded",
                      "detail": {
                        "reason": "quota_exceeded",
                        "limit": "monthly_quota"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error. Never marked with the Sandbox header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Internal error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/export": {
      "get": {
        "operationId": "exportYear",
        "x-pholidays-feature": "bulk_export",
        "tags": [
          "Holidays"
        ],
        "summary": "Bulk year export",
        "description": "Every row of a year in one answer: nationwide and local, all statuses (`confirmed`, `projected`, `pending`), with no `limit`. Ordered by date, then source rank, then name. Business Plan only: a Pro key gets `403 feature_not_in_plan`, and the body names the Business Plan. Counted as one request.",
        "parameters": [
          {
            "name": "year",
            "in": "query",
            "required": true,
            "description": "The year to export.",
            "schema": {
              "type": "integer",
              "minimum": 2000,
              "maximum": 2100,
              "examples": [
                2026
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Every holiday of the year.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "Edge-cached; cache on your side too. A Sandbox key gets `private, no-store` instead."
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On every 200 to a Sandbox key, cache HIT included.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Holiday"
                      }
                    },
                    "meta": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Meta"
                        },
                        {
                          "type": "object",
                          "required": [
                            "count"
                          ],
                          "properties": {
                            "count": {
                              "type": "integer",
                              "description": "How many items `data` holds.",
                              "examples": [
                                1
                              ]
                            }
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "year"
                          ],
                          "properties": {
                            "year": {
                              "type": "integer",
                              "description": "The exported year."
                            }
                          }
                        }
                      ]
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": 1,
                      "date": "2026-01-01",
                      "weekday": "Thursday",
                      "year": 2026,
                      "name": "New Year's Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 2,
                      "date": "2026-02-17",
                      "weekday": "Tuesday",
                      "year": 2026,
                      "name": "Chinese New Year",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/wp-content/uploads/2025/09/20250903-PROC-1006-FRM.pdf.pdf",
                      "isMovable": false,
                      "notes": null,
                      "source": "manual",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 3,
                      "date": "2026-02-25",
                      "weekday": "Wednesday",
                      "year": 2026,
                      "name": "EDSA People Power Revolution Anniversary",
                      "type": "special_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1,
                        "note": "Treated as an ordinary working day."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 4,
                      "date": "2026-03-20",
                      "weekday": "Friday",
                      "year": 2026,
                      "name": "Eid'l Fitr",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1189, s. 2026",
                      "sourceUrl": "https://pco.gov.ph/news_releases/proclamation-no-1189-s-2026-declaring-friday-20-march-2026-a-regular-holiday-throughout-the-country-in-observance-of-eidl-fitr-feast-of-ramadhan/",
                      "isMovable": true,
                      "notes": "Declared by Proclamation No. 1189, s. 2026 (Proclamation No. 1006, s. 2025 left the date to a later proclamation).",
                      "source": "manual",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 5,
                      "date": "2026-04-02",
                      "weekday": "Thursday",
                      "year": 2026,
                      "name": "Maundy Thursday",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 6,
                      "date": "2026-04-03",
                      "weekday": "Friday",
                      "year": 2026,
                      "name": "Good Friday",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 7,
                      "date": "2026-04-04",
                      "weekday": "Saturday",
                      "year": 2026,
                      "name": "Black Saturday",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 8,
                      "date": "2026-04-09",
                      "weekday": "Thursday",
                      "year": 2026,
                      "name": "Araw ng Kagitingan",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 9,
                      "date": "2026-05-01",
                      "weekday": "Friday",
                      "year": 2026,
                      "name": "Labor Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 10,
                      "date": "2026-05-27",
                      "weekday": "Wednesday",
                      "year": 2026,
                      "name": "Eid'l Adha",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1264, s. 2026",
                      "sourceUrl": "https://pco.gov.ph/news_releases/proclamation-no-1264-s-2026/",
                      "isMovable": true,
                      "notes": "Declared by Proclamation No. 1264, s. 2026 (Proclamation No. 1006, s. 2025 left the date to a later proclamation).",
                      "source": "manual",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 11,
                      "date": "2026-06-12",
                      "weekday": "Friday",
                      "year": 2026,
                      "name": "Independence Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 12,
                      "date": "2026-08-21",
                      "weekday": "Friday",
                      "year": 2026,
                      "name": "Ninoy Aquino Day",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 13,
                      "date": "2026-08-31",
                      "weekday": "Monday",
                      "year": 2026,
                      "name": "National Heroes Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 14,
                      "date": "2026-11-01",
                      "weekday": "Sunday",
                      "year": 2026,
                      "name": "All Saints' Day",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 15,
                      "date": "2026-11-02",
                      "weekday": "Monday",
                      "year": 2026,
                      "name": "All Souls' Day",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 16,
                      "date": "2026-11-30",
                      "weekday": "Monday",
                      "year": 2026,
                      "name": "Bonifacio Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 17,
                      "date": "2026-12-08",
                      "weekday": "Tuesday",
                      "year": 2026,
                      "name": "Feast of the Immaculate Conception of Mary",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 18,
                      "date": "2026-12-24",
                      "weekday": "Thursday",
                      "year": 2026,
                      "name": "Christmas Eve",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/wp-content/uploads/2025/09/20250903-PROC-1006-FRM.pdf.pdf",
                      "isMovable": false,
                      "notes": null,
                      "source": "manual",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 19,
                      "date": "2026-12-25",
                      "weekday": "Friday",
                      "year": 2026,
                      "name": "Christmas Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 20,
                      "date": "2026-12-30",
                      "weekday": "Wednesday",
                      "year": 2026,
                      "name": "Rizal Day",
                      "type": "regular",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 1,
                        "worked": 2,
                        "note": "Paid even if unworked; 200% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    },
                    {
                      "id": 21,
                      "date": "2026-12-31",
                      "weekday": "Thursday",
                      "year": 2026,
                      "name": "Last Day of the Year",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1006, s. 2025",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-regular-holidays-special-non-working-days-for-2026/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper",
                      "premiumPay": {
                        "unworked": 0,
                        "worked": 1.3,
                        "note": "No work, no pay (unless company policy); 130% of daily wage if worked."
                      },
                      "updatedAt": "2026-01-15T08:30:00.000Z"
                    }
                  ],
                  "meta": {
                    "lastUpdated": "2026-01-15T08:30:00.000Z",
                    "lastChecked": "2026-12-25T02:00:00.000Z",
                    "coverage": "Nationwide holidays are tracked per annual proclamation. Years without a proclamation are rule-based projections (status \"projected\"). Eid dates stay \"pending\" until proclaimed. Local holidays are best-effort and incomplete.",
                    "officialSource": "https://www.officialgazette.gov.ph/nationwide-holidays/",
                    "count": 21,
                    "year": 2026
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters; `detail` holds the field errors. `year` is required. A Sandbox key gets the same `400`, marked.",
            "headers": {
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On a 400 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid query parameters",
                  "detail": {
                    "year": [
                      "Invalid input"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "The key is invalid or revoked, or none was sent. `detail.reason` is `invalid_key` or `no_key` (with `detail.pricing`). A call with no key always gets `no_key` on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, which never served one. On the other routes it gets `no_key` only once an announced sunset date has passed; until then it is served, with `Deprecation`, `Sunset` and `Link` headers. Never marked with the Sandbox header.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "invalid_key": {
                    "summary": "The key is unknown, revoked or expired",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "invalid_key"
                      }
                    }
                  },
                  "no_key": {
                    "summary": "No key: always on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, on every other route after an announced sunset date",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "no_key",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Refused. `detail.reason` is `feature_not_in_plan` (a Pro key (its Plan does not include `bulk_export`) or a Sandbox key; `detail.feature`, `detail.plan` and `detail.pricing` say which feature is missing, the cheapest Plan that has it and where to buy it), `past_due` (payment failed and the 7-day grace is over) or `subscription_inactive` (no active subscription).",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On the Sandbox refusals (`feature_not_in_plan`).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "feature_not_in_plan": {
                    "summary": "A Pro key (its Plan does not include `bulk_export`) or a Sandbox key",
                    "value": {
                      "error": "Not included with this key: Bulk year export. The Business Plan includes it. See https://pholidays.com/pricing",
                      "detail": {
                        "reason": "feature_not_in_plan",
                        "feature": "bulk_export",
                        "plan": "business",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  },
                  "past_due": {
                    "summary": "Payment failed and the 7-day grace is over",
                    "value": {
                      "error": "Subscription inactive, update payment",
                      "detail": {
                        "reason": "past_due"
                      }
                    }
                  },
                  "subscription_inactive": {
                    "summary": "No active subscription",
                    "value": {
                      "error": "Subscription inactive",
                      "detail": {
                        "reason": "subscription_inactive"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over a limit; wait `Retry-After` seconds. For a Client key `detail.limit` is `per_minute` (reason `rate_limited`, 60 seconds) or `monthly_quota` (reason `quota_exceeded`, until the next month); for a Sandbox key it is `daily_quota` (until the next UTC day). A per-IP limit returns `{ error }` with no `detail` and 60 seconds.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "examples": [
                    60
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. Only on the `daily_quota` 429 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Over the per-minute limit",
                    "value": {
                      "error": "Per-minute rate limit exceeded",
                      "detail": {
                        "reason": "rate_limited",
                        "limit": "per_minute"
                      }
                    }
                  },
                  "quota_exceeded": {
                    "summary": "Over the monthly quota (for a Sandbox key, the daily quota)",
                    "value": {
                      "error": "Monthly quota exceeded",
                      "detail": {
                        "reason": "quota_exceeded",
                        "limit": "monthly_quota"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error. Never marked with the Sandbox header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Internal error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/calendar.ics": {
      "get": {
        "operationId": "calendarFeed",
        "x-pholidays-feature": "ical",
        "tags": [
          "Calendar"
        ],
        "summary": "iCal feed",
        "description": "An iCalendar (RFC 5545) file of the holidays, one all-day event per holiday, for a calendar app. Business Plan only: a Pro key gets `403 feature_not_in_plan`, and the body names the Business Plan.\n\nWithout `year` it covers the current and next year (Asia/Manila). Without `locality` (or with `nationwide`) it has the nationwide holidays; with a locality, those plus that locality's.\n\n**Auth**: a script or HTTP client sends a Client key in the `x-api-key` header (or `Authorization: Bearer`). A calendar app cannot send a header, so it subscribes with a **Feed token** in the URL instead: `?token=phf_…`, created at `https://pholidays.com/account`. The Feed token works on this route only and is not a Client key; a Client key in the URL is refused. Anyone with the URL reads the feed, so treat it as a credential and regenerate or revoke it at `/account`. Every fetch is a request counted against your Plan, like any other call, so keep a subscription's refresh interval long (the feed asks for one day).\n\nEach status is told apart three ways, so a client that drops one of them still shows the difference:\n\n- `confirmed`: `STATUS:CONFIRMED`, `SUMMARY` is the name.\n- `projected`: `STATUS:TENTATIVE`, `SUMMARY` ends in `(projected)`.\n- `pending`: `STATUS:TENTATIVE`, `SUMMARY` ends in `(pending proclamation)`.\n\nEvery event also has `X-PHOLIDAYS-STATUS` and `X-PHOLIDAYS-TYPE` with the API values. `UID` is `<date>-<locality>-<name slug>@pholidays.com`: stable for as long as the holiday exists, so a re-import updates an event instead of duplicating it. A holiday that is re-dated is a new event.",
        "parameters": [
          {
            "name": "year",
            "in": "query",
            "description": "Defaults to the current and next year in Asia/Manila.",
            "schema": {
              "type": "integer",
              "minimum": 2000,
              "maximum": 2100,
              "examples": [
                2026
              ]
            }
          },
          {
            "name": "locality",
            "in": "query",
            "description": "Free-text locality slug, e.g. `talisay-batangas`. Adds that locality to the nationwide holidays; when omitted, only nationwide holidays are returned.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "x-pholidays-feature": "local_filters"
          },
          {
            "name": "token",
            "in": "query",
            "description": "Feed token from https://pholidays.com/account, for calendar apps that cannot send a header. This route only. A credential: anyone with the URL reads the feed; regenerate or revoke it at /account.",
            "schema": {
              "type": "string",
              "examples": [
                "phf_0000000000000000000000000000000000000000"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "An iCalendar file. The example is cut to the first three events.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "Edge-cached; cache on your side too."
              },
              "Content-Disposition": {
                "schema": {
                  "type": "string"
                },
                "description": "An attachment named for the years and locality, e.g. `pholidays-2026-2027.ics`."
              }
            },
            "content": {
              "text/calendar": {
                "schema": {
                  "type": "string"
                },
                "example": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//pholidays//PH Holidays API//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-CALNAME:PH Holidays 2026\r\nREFRESH-INTERVAL;VALUE=DURATION:P1D\r\nX-PUBLISHED-TTL:P1D\r\nBEGIN:VEVENT\r\nUID:2026-01-01-nationwide-new-years-day@pholidays.com\r\nDTSTAMP:20260115T083000Z\r\nLAST-MODIFIED:20260115T083000Z\r\nDTSTART;VALUE=DATE:20260101\r\nDTEND;VALUE=DATE:20260102\r\nSUMMARY:New Year's Day\r\nDESCRIPTION:Type: Regular holiday\\nStatus: Confirmed\\nProclamation: 1006\\, \r\n s. 2025\\nSource: https://pco.gov.ph/news_releases/pbbm-issues-proclamation\r\n -declaring-regular-holidays-special-non-working-days-for-2026/\r\nSTATUS:CONFIRMED\r\nCATEGORIES:Regular holiday,Confirmed\r\nTRANSP:TRANSPARENT\r\nURL:https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-reg\r\n ular-holidays-special-non-working-days-for-2026/\r\nX-PHOLIDAYS-STATUS:confirmed\r\nX-PHOLIDAYS-TYPE:regular\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:2026-02-17-nationwide-chinese-new-year@pholidays.com\r\nDTSTAMP:20260115T083000Z\r\nLAST-MODIFIED:20260115T083000Z\r\nDTSTART;VALUE=DATE:20260217\r\nDTEND;VALUE=DATE:20260218\r\nSUMMARY:Chinese New Year\r\nDESCRIPTION:Type: Special non-working day\\nStatus: Confirmed\\nProclamation:\r\n  1006\\, s. 2025\\nSource: https://pco.gov.ph/wp-content/uploads/2025/09/202\r\n 50903-PROC-1006-FRM.pdf.pdf\r\nSTATUS:CONFIRMED\r\nCATEGORIES:Special non-working day,Confirmed\r\nTRANSP:TRANSPARENT\r\nURL:https://pco.gov.ph/wp-content/uploads/2025/09/20250903-PROC-1006-FRM.pd\r\n f.pdf\r\nX-PHOLIDAYS-STATUS:confirmed\r\nX-PHOLIDAYS-TYPE:special_non_working\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:2026-02-25-nationwide-edsa-people-power-revolution-anniversary@pholiday\r\n s.com\r\nDTSTAMP:20260115T083000Z\r\nLAST-MODIFIED:20260115T083000Z\r\nDTSTART;VALUE=DATE:20260225\r\nDTEND;VALUE=DATE:20260226\r\nSUMMARY:EDSA People Power Revolution Anniversary\r\nDESCRIPTION:Type: Special working day\\nStatus: Confirmed\\nProclamation: 100\r\n 6\\, s. 2025\\nSource: https://pco.gov.ph/news_releases/pbbm-issues-proclama\r\n tion-declaring-regular-holidays-special-non-working-days-for-2026/\r\nSTATUS:CONFIRMED\r\nCATEGORIES:Special working day,Confirmed\r\nTRANSP:TRANSPARENT\r\nURL:https://pco.gov.ph/news_releases/pbbm-issues-proclamation-declaring-reg\r\n ular-holidays-special-non-working-days-for-2026/\r\nX-PHOLIDAYS-STATUS:confirmed\r\nX-PHOLIDAYS-TYPE:special_working\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"
              }
            }
          },
          "400": {
            "description": "Invalid query parameters; `detail` holds the field errors. A Sandbox key gets the same `400`, marked.",
            "headers": {
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On a 400 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid query parameters",
                  "detail": {
                    "year": [
                      "Invalid input"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "The key is invalid or revoked, or none was sent. `detail.reason` is `invalid_key` or `no_key` (with `detail.pricing`). A call with no key always gets `no_key` on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, which never served one. On the other routes it gets `no_key` only once an announced sunset date has passed; until then it is served, with `Deprecation`, `Sunset` and `Link` headers. Never marked with the Sandbox header.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "invalid_key": {
                    "summary": "The key is unknown, revoked or expired",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "invalid_key"
                      }
                    }
                  },
                  "no_key": {
                    "summary": "No key: always on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, on every other route after an announced sunset date",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "no_key",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Refused. `detail.reason` is `feature_not_in_plan` (a Pro key (its Plan does not include `ical`) or a Sandbox key; `detail.feature`, `detail.plan` and `detail.pricing` say which feature is missing, the cheapest Plan that has it and where to buy it), `past_due` (payment failed and the 7-day grace is over) or `subscription_inactive` (no active subscription).",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On the Sandbox refusals (`feature_not_in_plan`).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "feature_not_in_plan": {
                    "summary": "A Pro key (its Plan does not include `ical`) or a Sandbox key",
                    "value": {
                      "error": "Not included with this key: iCal feed. The Business Plan includes it. See https://pholidays.com/pricing",
                      "detail": {
                        "reason": "feature_not_in_plan",
                        "feature": "ical",
                        "plan": "business",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  },
                  "past_due": {
                    "summary": "Payment failed and the 7-day grace is over",
                    "value": {
                      "error": "Subscription inactive, update payment",
                      "detail": {
                        "reason": "past_due"
                      }
                    }
                  },
                  "subscription_inactive": {
                    "summary": "No active subscription",
                    "value": {
                      "error": "Subscription inactive",
                      "detail": {
                        "reason": "subscription_inactive"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over a limit; wait `Retry-After` seconds. For a Client key `detail.limit` is `per_minute` (reason `rate_limited`, 60 seconds) or `monthly_quota` (reason `quota_exceeded`, until the next month); for a Sandbox key it is `daily_quota` (until the next UTC day). A per-IP limit returns `{ error }` with no `detail` and 60 seconds.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "examples": [
                    60
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. Only on the `daily_quota` 429 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Over the per-minute limit",
                    "value": {
                      "error": "Per-minute rate limit exceeded",
                      "detail": {
                        "reason": "rate_limited",
                        "limit": "per_minute"
                      }
                    }
                  },
                  "quota_exceeded": {
                    "summary": "Over the monthly quota (for a Sandbox key, the daily quota)",
                    "value": {
                      "error": "Monthly quota exceeded",
                      "detail": {
                        "reason": "quota_exceeded",
                        "limit": "monthly_quota"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error. Never marked with the Sandbox header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Internal error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/changes": {
      "get": {
        "operationId": "listChanges",
        "x-pholidays-feature": "changes",
        "tags": [
          "Holidays"
        ],
        "summary": "Change feed",
        "description": "What changed in the dataset since a time, oldest first, with no `limit`. Poll it to keep your copy current. Included in the Pro and Business Plans: a Sandbox key gets `403 feature_not_in_plan`, and a call with no key gets `401` (`no_key`).\n\nEach item is a **Change record**: the holiday row as it was after the change (as it was before it, for `removed`), with its `status`, `sourceUrl`, `isMovable` and `notes`. `change` is one of:\n\n- `added`: a new row. `previousStatus` is `null`.\n- `changed`: a row was edited (name, type, locality, links, notes or source) and its status did not move. `previousStatus` equals `status`.\n- `status_changed`: a move between `projected`, `pending` and `confirmed`. `previousStatus` is the old status.\n- `removed`: a row is gone, including a projection dropped when the official list arrives. `status` and `previousStatus` are the status it had.\n\nA holiday that a Source or a manual correction re-dates is a `removed` (old date) plus an `added` (new date, a new `holidayId`), because the date is part of the row. A pending holiday proclaimed on a different date appears that way too: a `removed` with status `pending`, then an `added` with status `confirmed`.\n\n`since` is exclusive: a record at exactly that time is not returned. Pass `meta.latest` back as `since` on your next poll (`null` when nothing changed: keep your `since`). A record is returned only once its millisecond has passed, so one made a moment ago shows up on the next poll, and polling with `meta.latest` never skips a record. Records exist from the day the feed shipped; there is no history before that. Counted as one request.",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "required": true,
            "description": "An ISO 8601 date-time with seconds and either `Z` or a `±HH:MM` offset, e.g. `2026-12-01T00:00:00Z` or `2026-12-01T08:00:00+08:00`. A bare date, a time with no zone, or a UTC year outside 0000-9999 is a `400`. `meta.since` echoes it in UTC.",
            "schema": {
              "type": "string",
              "format": "date-time",
              "examples": [
                "2026-12-01T00:00:00Z"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The Change records after `since`, oldest first.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "Edge-cached; cache on your side too. A Sandbox key gets `private, no-store` instead."
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On every 200 to a Sandbox key, cache HIT included.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/HolidayChange"
                      }
                    },
                    "meta": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Meta"
                        },
                        {
                          "type": "object",
                          "required": [
                            "count"
                          ],
                          "properties": {
                            "count": {
                              "type": "integer",
                              "description": "How many items `data` holds.",
                              "examples": [
                                1
                              ]
                            }
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "since",
                            "latest"
                          ],
                          "properties": {
                            "since": {
                              "type": "string",
                              "format": "date-time",
                              "description": "The requested `since`, in UTC (millisecond precision)."
                            },
                            "latest": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "date-time",
                              "description": "The `changedAt` of the last record returned, or `null` when there is none. Pass it as `since` on your next poll."
                            }
                          }
                        }
                      ]
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": 41,
                      "changedAt": "2026-12-01T02:00:05.123Z",
                      "change": "status_changed",
                      "holidayId": 22,
                      "date": "2027-01-01",
                      "year": 2027,
                      "name": "New Year's Day",
                      "type": "regular",
                      "status": "confirmed",
                      "previousStatus": "projected",
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1427, s. 2026",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-declares-2027-regular-and-special-holidays/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper"
                    },
                    {
                      "id": 42,
                      "changedAt": "2026-12-01T02:00:05.124Z",
                      "change": "added",
                      "holidayId": 36,
                      "date": "2027-11-02",
                      "year": 2027,
                      "name": "All Souls' Day",
                      "type": "special_non_working",
                      "status": "confirmed",
                      "previousStatus": null,
                      "locality": "nationwide",
                      "region": null,
                      "province": null,
                      "proclamationNumber": "1427, s. 2026",
                      "sourceUrl": "https://pco.gov.ph/news_releases/pbbm-declares-2027-regular-and-special-holidays/",
                      "isMovable": false,
                      "notes": null,
                      "source": "scraper"
                    }
                  ],
                  "meta": {
                    "lastUpdated": "2026-01-15T08:30:00.000Z",
                    "lastChecked": "2026-12-25T02:00:00.000Z",
                    "coverage": "Nationwide holidays are tracked per annual proclamation. Years without a proclamation are rule-based projections (status \"projected\"). Eid dates stay \"pending\" until proclaimed. Local holidays are best-effort and incomplete.",
                    "officialSource": "https://www.officialgazette.gov.ph/nationwide-holidays/",
                    "count": 2,
                    "since": "2026-12-01T00:00:00.000Z",
                    "latest": "2026-12-01T02:00:05.124Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters; `detail` holds the field errors. `since` is required and must be a date-time with a zone. A Sandbox key gets the same `400`, marked.",
            "headers": {
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On a 400 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid query parameters",
                  "detail": {
                    "since": [
                      "Invalid ISO datetime"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "The key is invalid or revoked, or none was sent. `detail.reason` is `invalid_key` or `no_key` (with `detail.pricing`). A call with no key always gets `no_key` on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, which never served one. On the other routes it gets `no_key` only once an announced sunset date has passed; until then it is served, with `Deprecation`, `Sunset` and `Link` headers. Never marked with the Sandbox header.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "invalid_key": {
                    "summary": "The key is unknown, revoked or expired",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "invalid_key"
                      }
                    }
                  },
                  "no_key": {
                    "summary": "No key: always on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, on every other route after an announced sunset date",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "no_key",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Refused. `detail.reason` is `feature_not_in_plan` (a Sandbox key (it serves the core lookups only); `detail.feature`, `detail.plan` and `detail.pricing` say which feature is missing, the cheapest Plan that has it and where to buy it), `past_due` (payment failed and the 7-day grace is over) or `subscription_inactive` (no active subscription).",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On the Sandbox refusals (`feature_not_in_plan`).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "feature_not_in_plan": {
                    "summary": "A Sandbox key (it serves the core lookups only)",
                    "value": {
                      "error": "Not included with this key: Change feed. The Pro Plan includes it. See https://pholidays.com/pricing",
                      "detail": {
                        "reason": "feature_not_in_plan",
                        "feature": "changes",
                        "plan": "pro",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  },
                  "past_due": {
                    "summary": "Payment failed and the 7-day grace is over",
                    "value": {
                      "error": "Subscription inactive, update payment",
                      "detail": {
                        "reason": "past_due"
                      }
                    }
                  },
                  "subscription_inactive": {
                    "summary": "No active subscription",
                    "value": {
                      "error": "Subscription inactive",
                      "detail": {
                        "reason": "subscription_inactive"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over a limit; wait `Retry-After` seconds. For a Client key `detail.limit` is `per_minute` (reason `rate_limited`, 60 seconds) or `monthly_quota` (reason `quota_exceeded`, until the next month); for a Sandbox key it is `daily_quota` (until the next UTC day). A per-IP limit returns `{ error }` with no `detail` and 60 seconds.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "examples": [
                    60
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. Only on the `daily_quota` 429 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Over the per-minute limit",
                    "value": {
                      "error": "Per-minute rate limit exceeded",
                      "detail": {
                        "reason": "rate_limited",
                        "limit": "per_minute"
                      }
                    }
                  },
                  "quota_exceeded": {
                    "summary": "Over the monthly quota (for a Sandbox key, the daily quota)",
                    "value": {
                      "error": "Monthly quota exceeded",
                      "detail": {
                        "reason": "quota_exceeded",
                        "limit": "monthly_quota"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error. Never marked with the Sandbox header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Internal error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/coverage": {
      "get": {
        "operationId": "coverage",
        "tags": [
          "Meta"
        ],
        "summary": "Dataset coverage",
        "description": "Counts by year/status and the last-checked time of every Source. It also reports when the data was last verified against the proclamations, and which current and next year nationwide holidays are Unverified holidays (names, no dates). Cached for a shorter time than other endpoints. Needs a key like the others. It is open to a Sandbox key: it returns counts, Source status and that verification summary, never holiday rows.",
        "responses": {
          "200": {
            "description": "Coverage report.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "Edge-cached; cache on your side too. A Sandbox key gets `private, no-store` instead."
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. On every 200 to a Sandbox key, cache HIT included.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              },
              "Deprecation": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 9745: `@` followed by Unix seconds, 60 days before `Sunset`. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "Present only on a call with no key, once a sunset date is announced. RFC 8594 HTTP-date: when calls with no key start to get a `401`. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              },
              "Link": {
                "description": "Present only on a call with no key, once a sunset date is announced. `<https://pholidays.com/pricing>; rel=\"sunset\"`: where to get a key. On a 200 to a call with no key, cache HIT included.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "years",
                        "sources",
                        "verification"
                      ],
                      "properties": {
                        "years": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "required": [
                              "year",
                              "confirmed",
                              "projected",
                              "pending",
                              "local"
                            ],
                            "properties": {
                              "year": {
                                "type": "integer"
                              },
                              "confirmed": {
                                "type": "integer"
                              },
                              "projected": {
                                "type": "integer"
                              },
                              "pending": {
                                "type": "integer"
                              },
                              "local": {
                                "type": "integer"
                              }
                            }
                          }
                        },
                        "sources": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "required": [
                              "url",
                              "kind",
                              "lastCheckedAt",
                              "lastChangedAt",
                              "lastError",
                              "recordsFound"
                            ],
                            "properties": {
                              "url": {
                                "type": "string"
                              },
                              "kind": {
                                "type": "string"
                              },
                              "lastCheckedAt": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "lastChangedAt": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "lastError": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "recordsFound": {
                                "type": "integer"
                              }
                            }
                          }
                        },
                        "verification": {
                          "type": "object",
                          "required": [
                            "lastVerified",
                            "years",
                            "unverified"
                          ],
                          "properties": {
                            "lastVerified": {
                              "type": "string",
                              "format": "date"
                            },
                            "years": {
                              "type": "array",
                              "items": {
                                "type": "integer"
                              },
                              "description": "The years the Last verified check covered."
                            },
                            "unverified": {
                              "type": "array",
                              "description": "Unverified holidays: nationwide holidays whose status is not confirmed, or that are confirmed with no `sourceUrl`, always for the current and next year (Manila), whatever `years` says.",
                              "items": {
                                "type": "object",
                                "required": [
                                  "year",
                                  "name",
                                  "status",
                                  "reason"
                                ],
                                "properties": {
                                  "year": {
                                    "type": "integer"
                                  },
                                  "name": {
                                    "type": "string"
                                  },
                                  "status": {
                                    "type": "string",
                                    "enum": [
                                      "projected",
                                      "pending",
                                      "confirmed"
                                    ]
                                  },
                                  "reason": {
                                    "type": "string"
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                },
                "example": {
                  "data": {
                    "years": [
                      {
                        "year": 2026,
                        "confirmed": 21,
                        "projected": 0,
                        "pending": 0,
                        "local": 0
                      },
                      {
                        "year": 2027,
                        "confirmed": 19,
                        "projected": 0,
                        "pending": 2,
                        "local": 0
                      }
                    ],
                    "sources": [
                      {
                        "url": "https://pco.gov.ph/?s=regular+and+special+holidays",
                        "kind": "proclamation_index",
                        "lastCheckedAt": "2026-12-25T02:00:00.000Z",
                        "lastChangedAt": null,
                        "lastError": null,
                        "recordsFound": 0
                      },
                      {
                        "url": "https://pco.gov.ph/news_releases/feed/?tag=holidays",
                        "kind": "proclamation_index",
                        "lastCheckedAt": "2026-12-25T02:00:00.000Z",
                        "lastChangedAt": null,
                        "lastError": null,
                        "recordsFound": 0
                      },
                      {
                        "url": "https://pco.gov.ph/feed/?s=eid",
                        "kind": "proclamation_index",
                        "lastCheckedAt": "2026-12-25T02:00:00.000Z",
                        "lastChangedAt": null,
                        "lastError": null,
                        "recordsFound": 0
                      },
                      {
                        "url": "https://www.officialgazette.gov.ph/feed/",
                        "kind": "proclamation_index",
                        "lastCheckedAt": "2026-12-25T02:00:00.000Z",
                        "lastChangedAt": null,
                        "lastError": null,
                        "recordsFound": 0
                      }
                    ],
                    "verification": {
                      "lastVerified": "2026-10-14",
                      "years": [
                        2026,
                        2027
                      ],
                      "unverified": [
                        {
                          "year": 2027,
                          "name": "Eid'l Fitr",
                          "status": "pending",
                          "reason": "Date is an estimate until its own proclamation is issued (Eid dates follow the Islamic calendar)."
                        },
                        {
                          "year": 2027,
                          "name": "Eid'l Adha",
                          "status": "pending",
                          "reason": "Date is an estimate until its own proclamation is issued (Eid dates follow the Islamic calendar)."
                        }
                      ]
                    }
                  },
                  "meta": {
                    "lastUpdated": "2026-01-15T08:30:00.000Z",
                    "lastChecked": "2026-12-25T02:00:00.000Z",
                    "coverage": "Nationwide holidays are tracked per annual proclamation. Years without a proclamation are rule-based projections (status \"projected\"). Eid dates stay \"pending\" until proclaimed. Local holidays are best-effort and incomplete.",
                    "officialSource": "https://www.officialgazette.gov.ph/nationwide-holidays/"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The key is invalid or revoked, or none was sent. `detail.reason` is `invalid_key` or `no_key` (with `detail.pricing`). A call with no key always gets `no_key` on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, which never served one. On the other routes it gets `no_key` only once an announced sunset date has passed; until then it is served, with `Deprecation`, `Sunset` and `Link` headers. Never marked with the Sandbox header.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "invalid_key": {
                    "summary": "The key is unknown, revoked or expired",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "invalid_key"
                      }
                    }
                  },
                  "no_key": {
                    "summary": "No key: always on `/v1/export`, `/v1/calendar.ics` and `/v1/changes`, on every other route after an announced sunset date",
                    "value": {
                      "error": "API key required",
                      "detail": {
                        "reason": "no_key",
                        "pricing": "https://pholidays.com/pricing"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Refused. `detail.reason` is `past_due` (payment failed and the 7-day grace is over) or `subscription_inactive` (no active subscription).",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "past_due": {
                    "summary": "Payment failed and the 7-day grace is over",
                    "value": {
                      "error": "Subscription inactive, update payment",
                      "detail": {
                        "reason": "past_due"
                      }
                    }
                  },
                  "subscription_inactive": {
                    "summary": "No active subscription",
                    "value": {
                      "error": "Subscription inactive",
                      "detail": {
                        "reason": "subscription_inactive"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over a limit; wait `Retry-After` seconds. For a Client key `detail.limit` is `per_minute` (reason `rate_limited`, 60 seconds) or `monthly_quota` (reason `quota_exceeded`, until the next month); for a Sandbox key it is `daily_quota` (until the next UTC day). A per-IP limit returns `{ error }` with no `detail` and 60 seconds.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "examples": [
                    60
                  ]
                }
              },
              "x-pholidays-sandbox": {
                "description": "Present only for a Sandbox key. The one year it serves. Only on the `daily_quota` 429 to a Sandbox key.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    2024
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Over the per-minute limit",
                    "value": {
                      "error": "Per-minute rate limit exceeded",
                      "detail": {
                        "reason": "rate_limited",
                        "limit": "per_minute"
                      }
                    }
                  },
                  "quota_exceeded": {
                    "summary": "Over the monthly quota (for a Sandbox key, the daily quota)",
                    "value": {
                      "error": "Monthly quota exceeded",
                      "detail": {
                        "reason": "quota_exceeded",
                        "limit": "monthly_quota"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error. Never marked with the Sandbox header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Internal error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "A Client key (`phc_…`). The same key works as `Authorization: Bearer <key>`."
      },
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "A Client key (`phc_…`). The same key works in the `x-api-key` header."
      }
    },
    "schemas": {
      "HolidayType": {
        "type": "string",
        "enum": [
          "regular",
          "special_non_working",
          "special_working",
          "local"
        ]
      },
      "HolidayStatus": {
        "type": "string",
        "enum": [
          "confirmed",
          "projected",
          "pending"
        ]
      },
      "Holiday": {
        "type": "object",
        "required": [
          "id",
          "date",
          "weekday",
          "year",
          "name",
          "type",
          "status",
          "locality",
          "region",
          "province",
          "proclamationNumber",
          "sourceUrl",
          "isMovable",
          "notes",
          "source",
          "premiumPay",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "examples": [
              1
            ]
          },
          "date": {
            "type": "string",
            "format": "date",
            "examples": [
              "2026-04-03"
            ]
          },
          "weekday": {
            "type": "string",
            "examples": [
              "Friday"
            ]
          },
          "year": {
            "type": "integer",
            "examples": [
              2026
            ]
          },
          "name": {
            "type": "string",
            "examples": [
              "Good Friday"
            ]
          },
          "type": {
            "$ref": "#/components/schemas/HolidayType"
          },
          "status": {
            "$ref": "#/components/schemas/HolidayStatus"
          },
          "locality": {
            "type": "string",
            "examples": [
              "nationwide"
            ]
          },
          "region": {
            "type": [
              "string",
              "null"
            ]
          },
          "province": {
            "type": [
              "string",
              "null"
            ]
          },
          "proclamationNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "sourceUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "examples": [
              "https://www.officialgazette.gov.ph/nationwide-holidays/"
            ]
          },
          "isMovable": {
            "type": "boolean",
            "examples": [
              true
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "projection",
              "seed",
              "scraper",
              "manual"
            ]
          },
          "premiumPay": {
            "$ref": "#/components/schemas/PremiumPay"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "examples": [
              "2026-01-15T08:30:00.000Z"
            ]
          }
        }
      },
      "HolidayChange": {
        "type": "object",
        "description": "One Change record of the change feed: what happened to a holiday row, with its status and source link at that moment.",
        "required": [
          "id",
          "changedAt",
          "change",
          "holidayId",
          "date",
          "year",
          "name",
          "type",
          "status",
          "previousStatus",
          "locality",
          "region",
          "province",
          "proclamationNumber",
          "sourceUrl",
          "isMovable",
          "notes",
          "source"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Increases with time; breaks ties between records in the same millisecond.",
            "examples": [
              1
            ]
          },
          "changedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the change was recorded (UTC, millisecond precision).",
            "examples": [
              "2026-12-01T02:00:05.123Z"
            ]
          },
          "change": {
            "type": "string",
            "enum": [
              "added",
              "changed",
              "status_changed",
              "removed"
            ]
          },
          "holidayId": {
            "type": "integer",
            "description": "The `id` of the holiday row at that moment. A removed row's id no longer exists.",
            "examples": [
              1
            ]
          },
          "date": {
            "type": "string",
            "format": "date",
            "examples": [
              "2026-04-03"
            ]
          },
          "year": {
            "type": "integer",
            "examples": [
              2026
            ]
          },
          "name": {
            "type": "string",
            "examples": [
              "Good Friday"
            ]
          },
          "type": {
            "$ref": "#/components/schemas/HolidayType"
          },
          "status": {
            "$ref": "#/components/schemas/HolidayStatus"
          },
          "previousStatus": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/HolidayStatus"
              },
              {
                "type": "null"
              }
            ],
            "description": "The status before the change; `null` for `added`."
          },
          "locality": {
            "type": "string",
            "examples": [
              "nationwide"
            ]
          },
          "region": {
            "type": [
              "string",
              "null"
            ]
          },
          "province": {
            "type": [
              "string",
              "null"
            ]
          },
          "proclamationNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "sourceUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "examples": [
              "https://www.officialgazette.gov.ph/nationwide-holidays/"
            ]
          },
          "isMovable": {
            "type": "boolean",
            "examples": [
              true
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "projection",
              "seed",
              "scraper",
              "manual"
            ]
          }
        }
      },
      "PremiumPay": {
        "type": "object",
        "description": "What an employee earns on the day, as a multiple of the daily wage.",
        "required": [
          "unworked",
          "worked",
          "note"
        ],
        "properties": {
          "unworked": {
            "type": "number",
            "description": "Day not worked.",
            "examples": [
              1
            ]
          },
          "worked": {
            "type": "number",
            "description": "Day worked (first 8 hours).",
            "examples": [
              2
            ]
          },
          "note": {
            "type": "string",
            "examples": [
              "Paid even if unworked; 200% of daily wage if worked."
            ]
          }
        }
      },
      "LongWeekend": {
        "type": "object",
        "required": [
          "start",
          "end",
          "days",
          "label",
          "holidays"
        ],
        "properties": {
          "start": {
            "type": "string",
            "format": "date"
          },
          "end": {
            "type": "string",
            "format": "date"
          },
          "days": {
            "type": "integer"
          },
          "label": {
            "type": "string"
          },
          "holidays": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "date",
                "name",
                "type",
                "status"
              ],
              "properties": {
                "date": {
                  "type": "string",
                  "format": "date"
                },
                "name": {
                  "type": "string"
                },
                "type": {
                  "$ref": "#/components/schemas/HolidayType"
                },
                "status": {
                  "$ref": "#/components/schemas/HolidayStatus"
                }
              }
            }
          }
        }
      },
      "Meta": {
        "type": "object",
        "required": [
          "lastUpdated",
          "lastChecked",
          "coverage",
          "officialSource"
        ],
        "properties": {
          "lastUpdated": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2026-01-15T08:30:00.000Z"
            ]
          },
          "lastChecked": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2026-02-01T00:00:00.000Z"
            ]
          },
          "coverage": {
            "type": "string",
            "examples": [
              "Nationwide holidays are tracked per annual proclamation. Years without a proclamation are rule-based projections (status \"projected\"). Eid dates stay \"pending\" until proclaimed. Local holidays are best-effort and incomplete."
            ]
          },
          "officialSource": {
            "type": "string",
            "examples": [
              "https://www.officialgazette.gov.ph/nationwide-holidays/"
            ]
          },
          "sandbox": {
            "type": "object",
            "description": "Present only on a 200 to a Sandbox key.",
            "required": [
              "year",
              "requestsPerDay",
              "remainingToday",
              "note"
            ],
            "properties": {
              "year": {
                "type": "integer",
                "description": "The one year a Sandbox key serves.",
                "examples": [
                  2024
                ]
              },
              "requestsPerDay": {
                "type": "integer",
                "description": "The daily cap (UTC day).",
                "examples": [
                  100
                ]
              },
              "remainingToday": {
                "type": "integer",
                "description": "Requests left today after this one.",
                "examples": [
                  99
                ]
              },
              "note": {
                "type": "string",
                "description": "A reminder of the Sandbox rules and where to buy a Plan."
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "description": "A 400, 404 or 500 body. On a 400 `detail` holds the field errors.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "detail": {}
        }
      },
      "Refusal": {
        "type": "object",
        "description": "A 401, 403 or 429 body. A per-IP 429 has `error` only, no `detail`.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "examples": [
              "Monthly quota exceeded"
            ]
          },
          "detail": {
            "type": "object",
            "required": [
              "reason"
            ],
            "properties": {
              "reason": {
                "type": "string",
                "enum": [
                  "no_key",
                  "invalid_key",
                  "past_due",
                  "subscription_inactive",
                  "feature_not_in_plan",
                  "quota_exceeded",
                  "rate_limited",
                  "sandbox_year_only"
                ],
                "description": "Why the call was refused."
              },
              "limit": {
                "type": "string",
                "enum": [
                  "monthly_quota",
                  "per_minute",
                  "daily_quota"
                ],
                "description": "Only on a 429: the limit that was hit."
              },
              "year": {
                "type": "integer",
                "description": "Only on `sandbox_year_only`: the year a Sandbox key serves.",
                "examples": [
                  2024
                ]
              },
              "feature": {
                "type": "string",
                "enum": [
                  "core",
                  "workday",
                  "local_filters",
                  "changes",
                  "source_links",
                  "bulk_export",
                  "ical"
                ],
                "description": "Only on `feature_not_in_plan`: the Plan feature the route needs.",
                "examples": [
                  "bulk_export"
                ]
              },
              "plan": {
                "type": "string",
                "enum": [
                  "pro",
                  "business"
                ],
                "description": "Only on `feature_not_in_plan`: the cheapest Plan that has the feature.",
                "examples": [
                  "business"
                ]
              },
              "pricing": {
                "type": "string",
                "format": "uri",
                "description": "Only on `no_key`, `sandbox_year_only` and `feature_not_in_plan`: where to get a key or buy a Plan.",
                "examples": [
                  "https://pholidays.com/pricing"
                ]
              }
            }
          }
        }
      }
    }
  }
}