{
  "openapi": "3.1.0",
  "info": {
    "title": "Belief Calendar API",
    "version": "1.0.0",
    "summary": "Religious observance dates, with an explicit statement of how certain each one is.",
    "description": "Every date carries a `confidence` value, because religious dates are not all knowable to\nthe same standard. A Gregorian-fixed date cannot move. A date computed from the Hebrew\ncalendar is deterministic. An Islamic festival depends on a local moon sighting and may\nmove by a day, or differ between two communities in the same city.\n\nTreat `estimated` as provisional. Poll `/v1/observances` or subscribe to push updates to\nreceive the confirmed date once an authority announces it.\n\nDates that vary by country are returned per country. Pass `region` to get the reckoning\nthat applies to your people.",
    "contact": {
      "name": "Belief Calendar",
      "url": "https://beliefcalendar.com/about/"
    },
    "license": {
      "name": "Commercial. Access is granted under an Enterprise agreement."
    }
  },
  "servers": [
    {
      "url": "https://api.beliefcalendar.app"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Observances",
      "description": "Religious dates and the certainty attached to each."
    },
    {
      "name": "Traditions",
      "description": "The traditions covered and their subdivisions."
    },
    {
      "name": "Feeds",
      "description": "Calendar subscriptions in iCalendar format."
    },
    {
      "name": "Changes",
      "description": "Observances that have moved, so an integration can reconcile."
    },
    {
      "name": "Regions",
      "description": "The countries dates can be resolved for, and how each fixes its dates."
    },
    {
      "name": "Coverage",
      "description": "What the calendar does not yet compute, and why."
    }
  ],
  "paths": {
    "/v1/observances": {
      "get": {
        "tags": [
          "Observances"
        ],
        "operationId": "listObservances",
        "summary": "List observances in a period",
        "description": "Returns observances ordered by start date. Either `year`, or `from` and `to`, must be given.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "year",
            "in": "query",
            "description": "Calendar year.",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 2024,
              "maximum": 2035
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Start of the period, inclusive.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "End of the period, inclusive.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "region",
            "in": "query",
            "description": "ISO 3166-1 alpha-2 country code. Selects the local reckoning where one exists; omit for the default.",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[A-Z]{2}$",
              "examples": [
                "GB",
                "IL",
                "SA",
                "IN"
              ]
            }
          },
          {
            "name": "tradition",
            "in": "query",
            "description": "Filter to one tradition, by id.",
            "required": false,
            "schema": {
              "type": "string",
              "examples": [
                "islam",
                "judaism",
                "hinduism"
              ]
            }
          },
          {
            "name": "impact",
            "in": "query",
            "description": "Filter by scheduling impact.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "work-prohibited",
                "work-restricted",
                "observance-only",
                "commemorative"
              ]
            }
          },
          {
            "name": "confidence",
            "in": "query",
            "description": "Filter by how certain the date is.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "fixed",
                "computed",
                "estimated",
                "announced"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching observances.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Observance"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/ListMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The period was missing or malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The API key was missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key is not valid, or has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded for this key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/observances/{observanceId}": {
      "get": {
        "tags": [
          "Observances"
        ],
        "operationId": "getObservance",
        "summary": "Fetch one observance across years",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "observanceId",
            "in": "path",
            "description": "Observance id, such as `eid-al-fitr`.",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "First year to return.",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Last year to return.",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "region",
            "in": "query",
            "description": "ISO 3166-1 alpha-2 country code.",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[A-Z]{2}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The observance, one entry per year.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Observance"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No observance with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/traditions": {
      "get": {
        "tags": [
          "Traditions"
        ],
        "operationId": "listTraditions",
        "summary": "List the traditions covered",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Traditions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Tradition"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/traditions/{traditionId}": {
      "get": {
        "tags": [
          "Traditions"
        ],
        "operationId": "getTradition",
        "summary": "Fetch one tradition and its subdivisions",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "traditionId",
            "in": "path",
            "description": "Tradition id, such as `sikhism`.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The tradition.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tradition"
                }
              }
            }
          },
          "404": {
            "description": "No tradition with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/feeds/{region}.ics": {
      "get": {
        "tags": [
          "Feeds"
        ],
        "operationId": "getFeed",
        "summary": "Subscribe to a calendar feed",
        "description": "An iCalendar feed suitable for Outlook, Google Calendar or any CalDAV client. Entries carry the certainty and the scheduling impact in their description, and `X-CONFIDENCE` and `X-WORKPLACE-IMPACT` properties for machine use.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "region",
            "in": "path",
            "description": "ISO 3166-1 alpha-2 country code, or `all` for the default reckoning.",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tradition",
            "in": "query",
            "description": "Restrict the feed to one tradition.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "An iCalendar document.",
            "content": {
              "text/calendar": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No feed for that region.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/regions": {
      "get": {
        "tags": [
          "Regions"
        ],
        "operationId": "listRegions",
        "summary": "List the reckonings available",
        "description": "The countries dates can be resolved for, and how each one fixes its Islamic and Jewish dates. Use this to offer your users a country rather than hard-coding a list.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Supported regions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Region"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/changes": {
      "get": {
        "tags": [
          "Changes"
        ],
        "operationId": "listChanges",
        "summary": "Observances that have moved since a date",
        "description": "Everything whose date, length or certainty has changed since `since`, so an\nintegration can reconcile without refetching the year.\n\nThis is how a confirmed date reaches you. An Islamic festival is published as an\nestimate, and when the authority for your country announces it the entry is revised:\n`revision` increases and `confidence` becomes `announced`. Match on `id`, `year` and\n`region` and replace what you hold — the identity does not change when the date does.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "description": "Return changes made on or after this date.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "region",
            "in": "query",
            "description": "Restrict to one country.",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[A-Z]{2}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Changed observances, most recently changed first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Observance"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/ListMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`since` was missing or malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/coverage": {
      "get": {
        "tags": [
          "Coverage"
        ],
        "operationId": "getCoverage",
        "summary": "What is not computed, and why",
        "description": "Observances we deliberately do not emit, each with the reason. Published so that an integration can tell a known gap apart from a missing record.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Declared gaps.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CoverageGap"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Every request must carry the API key issued to your organisation.\n\nKeys are issued per organisation, may be rotated at any time, and are scoped to your\nagreed rate limit. Two keys can be live at once so a rotation needs no downtime.\nTreat a key as a credential: send it from your server, never from a browser or a\nmobile app, where it would be readable by anyone using it."
      }
    },
    "schemas": {
      "Observance": {
        "type": "object",
        "required": [
          "id",
          "name",
          "traditionId",
          "start",
          "end",
          "confidence",
          "workplaceImpact",
          "region"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "eid-al-fitr"
            ]
          },
          "name": {
            "type": "string",
            "examples": [
              "Eid al-Fitr"
            ]
          },
          "nativeName": {
            "type": "string",
            "examples": [
              "عِيد الفِطر"
            ]
          },
          "traditionId": {
            "type": "string",
            "examples": [
              "islam"
            ]
          },
          "branchIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "start": {
            "type": "string",
            "format": "date",
            "examples": [
              "2026-03-20"
            ]
          },
          "end": {
            "type": "string",
            "format": "date",
            "examples": [
              "2026-03-22"
            ]
          },
          "beginsEveningOf": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Present where the observance begins at sunset the evening before, as Jewish and Islamic days do. Book from this date, not from `start`, or you will miss the half of the observance people most often need off.",
            "examples": [
              "2026-03-19"
            ]
          },
          "confidence": {
            "type": "string",
            "enum": [
              "fixed",
              "computed",
              "estimated",
              "announced"
            ],
            "description": "`fixed` cannot move. `computed` follows a published rule. `estimated` depends on a sighting or an announcement and may move by a day, sometimes two. `announced` has been confirmed by a named authority."
          },
          "workplaceImpact": {
            "type": "string",
            "enum": [
              "work-prohibited",
              "work-restricted",
              "observance-only",
              "commemorative"
            ]
          },
          "region": {
            "type": "string",
            "description": "The reckoning this date was resolved for. `*` where the date is the same everywhere.",
            "examples": [
              "GB"
            ]
          },
          "authoritySourceId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The body that announced the date, where one did."
          },
          "revision": {
            "type": "integer",
            "description": "Increments whenever this observance moves. Compare it with the value you hold to detect a change; it matches the SEQUENCE in the iCalendar feed.",
            "examples": [
              0
            ]
          },
          "updatedAt": {
            "type": "string",
            "format": "date",
            "description": "When this observance last changed."
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Tradition": {
        "type": "object",
        "required": [
          "id",
          "name",
          "centralAuthority"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "sikhism"
            ]
          },
          "name": {
            "type": "string",
            "examples": [
              "Sikhism"
            ]
          },
          "centralAuthority": {
            "type": "boolean",
            "description": "False where no single body speaks for the tradition, which is the common case."
          },
          "subdivisionTerm": {
            "type": [
              "string",
              "null"
            ],
            "description": "The word this tradition uses for its subdivisions — denomination, branch, movement, tradition. Use it rather than a word of your own.",
            "examples": [
              "movement"
            ]
          },
          "branches": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Region": {
        "type": "object",
        "required": [
          "code",
          "name",
          "hijriMethod",
          "jewish"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2, or `*` for no country.",
            "examples": [
              "AE"
            ]
          },
          "name": {
            "type": "string",
            "examples": [
              "United Arab Emirates"
            ]
          },
          "hijriMethod": {
            "type": "string",
            "enum": [
              "umm-al-qura",
              "local-sighting",
              "calculation"
            ],
            "description": "`umm-al-qura` is the Gulf civil calendar and is predictable years ahead. `calculation` means the authority applies an astronomical criterion, so dates are computable but can differ from the Gulf. `local-sighting` means the date is only fixed when a committee accepts a sighting, usually a day or two beforehand — treat those as provisional."
          },
          "islamicAuthorityName": {
            "type": [
              "string",
              "null"
            ],
            "description": "The body that announces Islamic dates in this country."
          },
          "jewish": {
            "type": "string",
            "enum": [
              "israel",
              "diaspora"
            ],
            "description": "Festivals run an extra day outside the land of Israel."
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "CoverageGap": {
        "type": "object",
        "required": [
          "id",
          "name",
          "traditionId",
          "reason"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "guru-nanak-gurpurab"
            ]
          },
          "name": {
            "type": "string"
          },
          "traditionId": {
            "type": "string"
          },
          "reason": {
            "type": "string",
            "description": "Why this observance is not computed."
          }
        }
      },
      "ListMeta": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer"
          },
          "region": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "examples": [
                  "invalid_period"
                ]
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}
