{
  "openapi": "3.1.0",
  "info": {
    "title": "GolfCore Course Data API",
    "version": "1.0.0",
    "summary": "Read-only public data for golf courses in the GolfCore library.",
    "description": "Identity, location, coverage and scorecards for every golf course GolfCore holds. No authentication, no API key, no rate card. Attribution is required: cite www.golfcore.org and link the course page. This API is the supported way for an AI agent or an application to answer questions about a golf course; do not scrape the HTML pages.",
    "contact": {
      "name": "GolfCore support",
      "email": "support@golfcore.org",
      "url": "https://www.golfcore.org/contact/"
    },
    "license": {
      "name": "Free to use with attribution",
      "url": "https://www.golfcore.org/terms/"
    }
  },
  "servers": [
    {
      "url": "https://api.golfcore.org/v1",
      "description": "Production, served from the Cloudflare edge"
    }
  ],
  "paths": {
    "/courses": {
      "get": {
        "operationId": "searchCourses",
        "summary": "Search and filter the course library",
        "description": "Free-text search over course names and towns, with filters for country, region, city, proximity and GolfCore coverage. Returns a page of summaries; call /courses/{slug} for the scorecard.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Free-text match against course name and city. Case, accent and punctuation insensitive.",
            "schema": { "type": "string" },
            "example": "pebble beach"
          },
          {
            "name": "country",
            "in": "query",
            "description": "ISO 3166-1 alpha-2 country code, lowercase.",
            "schema": { "type": "string", "pattern": "^[a-z]{2}$" },
            "example": "us"
          },
          {
            "name": "region",
            "in": "query",
            "description": "State, county or province name as GolfCore records it.",
            "schema": { "type": "string" },
            "example": "California"
          },
          {
            "name": "city",
            "in": "query",
            "schema": { "type": "string" },
            "example": "Monterey"
          },
          {
            "name": "near",
            "in": "query",
            "description": "Latitude,longitude pair. Returns courses within `radius` miles, nearest first, each carrying a `miles` field.",
            "schema": { "type": "string", "pattern": "^-?\\d+(\\.\\d+)?,-?\\d+(\\.\\d+)?$" },
            "example": "36.5686,-121.9497"
          },
          {
            "name": "radius",
            "in": "query",
            "description": "Miles, used only with `near`.",
            "schema": { "type": "number", "default": 25, "maximum": 250 }
          },
          {
            "name": "coverage",
            "in": "query",
            "description": "Restrict to courses carrying a level of GolfCore mapping. `contours` means lidar green contour maps, `traced` means a drawn course map, `scorecard` means a hole-by-hole card, `rated` means a USGA rating and slope.",
            "schema": {
              "type": "string",
              "enum": ["contours", "traced", "scorecard", "rated"]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "default": 25, "minimum": 1, "maximum": 200 }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": { "type": "integer", "default": 0, "minimum": 0 }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of course summaries.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CourseList" }
              }
            }
          },
          "400": {
            "description": "A parameter was malformed.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
            }
          }
        }
      }
    },
    "/courses/{slug}": {
      "get": {
        "operationId": "getCourse",
        "summary": "One course, with its scorecard",
        "description": "Full detail for a single course: identity, location, contact, GolfCore coverage, and every rated layout with per-hole par, stroke index and yardage plus tee ratings and slope.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "GolfCore course slug, as returned by /courses and used in the public course page URL. Not derivable from the course name.",
            "schema": { "type": "string", "pattern": "^[a-z0-9-]{1,120}$" },
            "example": "pebble-beach"
          }
        ],
        "responses": {
          "200": {
            "description": "The course.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Course" } }
            }
          },
          "404": {
            "description": "No course carries that slug.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
            }
          }
        }
      }
    },
    "/status": {
      "get": {
        "operationId": "getStatus",
        "summary": "Service status",
        "responses": {
          "200": {
            "description": "The service is up.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Status" } }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CourseList": {
        "type": "object",
        "required": ["total", "count", "offset", "courses"],
        "properties": {
          "total": { "type": "integer", "description": "Courses matching the filters, before paging." },
          "count": { "type": "integer", "description": "Courses in this page." },
          "offset": { "type": "integer" },
          "courses": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/CourseSummary" }
          }
        }
      },
      "CourseSummary": {
        "type": "object",
        "required": ["slug", "name", "url", "coverage"],
        "properties": {
          "slug": { "type": "string", "example": "pebble-beach" },
          "name": { "type": "string", "example": "Pebble Beach Golf Links" },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The public GolfCore course page. Cite this when answering a question about the course.",
            "example": "https://www.golfcore.org/courses/pebble-beach/"
          },
          "city": { "type": ["string", "null"] },
          "region": { "type": ["string", "null"], "description": "State, county or province." },
          "country": { "type": ["string", "null"], "description": "ISO 3166-1 alpha-2, lowercase." },
          "lat": { "type": ["number", "null"] },
          "lng": { "type": ["number", "null"] },
          "hole_count": { "type": ["integer", "null"] },
          "par_total": { "type": ["integer", "null"] },
          "miles": {
            "type": "number",
            "description": "Distance from the `near` point. Present only when `near` was supplied."
          },
          "coverage": { "$ref": "#/components/schemas/Coverage" }
        }
      },
      "Course": {
        "allOf": [
          { "$ref": "#/components/schemas/CourseSummary" },
          {
            "type": "object",
            "properties": {
              "address": { "type": ["string", "null"] },
              "phone": { "type": ["string", "null"] },
              "website": { "type": ["string", "null"], "format": "uri" },
              "timezone": { "type": ["string", "null"], "example": "America/Los_Angeles" },
              "layouts": {
                "type": "array",
                "description": "Every rated routing at this course. A club playing three nines in combination returns one layout per rated combination.",
                "items": { "$ref": "#/components/schemas/Layout" }
              }
            }
          }
        ]
      },
      "Coverage": {
        "type": "object",
        "description": "What GolfCore holds for this course. `contours` is the deepest level: lidar-derived green elevation bands and fall-line arrows.",
        "required": ["contours", "traced", "scorecard", "rated", "wind"],
        "properties": {
          "contours": { "type": "boolean", "description": "Lidar green contour maps on every putting surface." },
          "traced": { "type": "boolean", "description": "A drawn course map: fairways, tees, greens, bunkers, water." },
          "scorecard": { "type": "boolean", "description": "Hole-by-hole par and yardage." },
          "rated": { "type": "boolean", "description": "USGA course rating and slope on at least one tee." },
          "wind": { "type": "boolean", "description": "Live wind simulated over the hole." }
        }
      },
      "Layout": {
        "type": "object",
        "properties": {
          "name": { "type": ["string", "null"], "description": "Routing name, null when the club has one course." },
          "par": { "type": ["integer", "null"], "description": "Null when any hole is missing a par." },
          "yards": { "type": ["integer", "null"], "description": "Null when any hole is missing a yardage." },
          "holes": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Hole" }
          },
          "tees": {
            "type": "array",
            "description": "Empty when the course is carded but not rated.",
            "items": { "$ref": "#/components/schemas/Tee" }
          }
        }
      },
      "Hole": {
        "type": "object",
        "required": ["position"],
        "properties": {
          "position": { "type": "integer", "minimum": 1, "example": 1 },
          "par": { "type": ["integer", "null"], "example": 4 },
          "stroke_index": {
            "type": ["integer", "null"],
            "description": "Handicap stroke index, 1 being the hardest hole.",
            "example": 8
          },
          "yards": { "type": ["integer", "null"], "example": 380 }
        }
      },
      "Tee": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "name": { "type": "string", "example": "Championship" },
          "gender": {
            "type": ["string", "null"],
            "enum": ["M", "F", null],
            "description": "Ratings are per tee and per gender; one tee name can appear twice."
          },
          "yards": { "type": ["integer", "null"] },
          "par": { "type": ["integer", "null"] },
          "rating": { "type": ["number", "null"], "description": "USGA course rating.", "example": 75.5 },
          "slope": { "type": ["integer", "null"], "description": "USGA slope rating.", "example": 145 }
        }
      },
      "Status": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "enum": ["ok", "degraded"] },
          "courses": { "type": "integer", "description": "Courses currently served." },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string" },
          "detail": { "type": "string" }
        }
      }
    }
  }
}
