{
  "openapi": "3.1.0",
  "info": {
    "title": "Married By Josh — Availability API",
    "summary": "Josh Withers' booked calendar dates, for couples and agents checking if a wedding date is free.",
    "description": "Read-only JSON feed of dates on which Josh Withers (wedding celebrant, Tasmania/Australia) is already booked. Derived from a private Google Calendar via Google's FreeBusy API, so only busy time ranges are exposed — no event details, attendees, or titles. An absent date in `busy_dates` does not guarantee availability in a binding sense — always confirm by sending an enquiry at https://marriedbyjosh.com/contact.",
    "version": "1.0.0",
    "contact": {
      "name": "Josh Withers",
      "email": "josh@withers.co",
      "url": "https://marriedbyjosh.com/contact"
    },
    "license": {
      "name": "CC BY 4.0",
      "url": "https://creativecommons.org/licenses/by/4.0/"
    },
    "termsOfService": "https://marriedbyjosh.com/privacy"
  },
  "servers": [
    { "url": "https://marriedbyjosh.com", "description": "Production" }
  ],
  "paths": {
    "/api/availability": {
      "get": {
        "summary": "Get Josh's booked (busy) dates",
        "description": "Returns every calendar date (in Australia/Hobart local time) over the next 18 months on which Josh has at least one existing commitment. Use this to tell couples whether the date they are considering is likely free. Cached at the edge; underlying calendar is refreshed at most once per hour.",
        "operationId": "getAvailability",
        "responses": {
          "200": {
            "description": "Availability payload.",
            "headers": {
              "Cache-Control": {
                "description": "Edge cache directive.",
                "schema": { "type": "string" }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Availability" },
                "example": {
                  "generated_at": "2026-04-24T11:37:24.679Z",
                  "timezone": "Australia/Hobart",
                  "horizon": { "from": "2026-04-24", "to": "2027-10-24" },
                  "busy_dates": ["2026-04-26", "2026-05-03", "2026-07-11"],
                  "busy_ranges": [
                    { "start": "2026-04-26T11:30:00+10:00", "end": "2026-04-26T13:00:00+10:00" }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Upstream calendar or auth failure. Safe to retry.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "debug",
            "in": "query",
            "required": false,
            "description": "When `1`, 503 error responses include a `detail` string with upstream error context. Remove before production use.",
            "schema": { "type": "string", "enum": ["1"] }
          }
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "Availability": {
        "type": "object",
        "required": ["generated_at", "timezone", "horizon", "busy_dates", "busy_ranges"],
        "properties": {
          "generated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When this payload was generated on the server."
          },
          "timezone": {
            "type": "string",
            "description": "IANA timezone used to derive calendar dates.",
            "example": "Australia/Hobart"
          },
          "horizon": {
            "type": "object",
            "required": ["from", "to"],
            "properties": {
              "from": { "type": "string", "format": "date" },
              "to": { "type": "string", "format": "date" }
            }
          },
          "busy_dates": {
            "type": "array",
            "description": "Sorted list of local calendar dates with at least one busy range overlapping them.",
            "items": { "type": "string", "format": "date" }
          },
          "busy_ranges": {
            "type": "array",
            "description": "Raw busy time ranges from Google FreeBusy, in ISO 8601 with timezone offset.",
            "items": {
              "type": "object",
              "required": ["start", "end"],
              "properties": {
                "start": { "type": "string", "format": "date-time" },
                "end": { "type": "string", "format": "date-time" }
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "string",
            "example": "availability_unavailable"
          },
          "detail": {
            "type": "string",
            "description": "Only present when ?debug=1."
          }
        }
      }
    }
  }
}
