{
  "openapi": "3.1.0",
  "info": {
    "title": "DiscGolfAPI",
    "version": "1.0.0",
    "description": "DiscGolfAPI is a read-only API for structured disc golf course data. It provides stable course records, location fields, country and region indexes, update metadata, confidence signals and attribution information for developer projects, maps, directories, websites and AI systems.",
    "termsOfService": "https://discgolfapi.com/terms/",
    "contact": {
      "name": "DiscGolfAPI",
      "url": "https://discgolfapi.com/contact/"
    },
    "license": {
      "name": "Free to use with attribution",
      "url": "https://discgolfapi.com/licence/"
    }
  },
  "servers": [
    {
      "url": "https://io.discgolfapi.com/v1",
      "description": "Production API"
    }
  ],
  "externalDocs": {
    "description": "DiscGolfAPI documentation",
    "url": "https://discgolfapi.com/docs/"
  },
  "tags": [
    {
      "name": "Courses",
      "description": "Course list and course detail endpoints."
    },
    {
      "name": "Countries",
      "description": "Country coverage index."
    },
    {
      "name": "Regions",
      "description": "Region, state and subdivision coverage index."
    },
    {
      "name": "Updates",
      "description": "Recent public data updates."
    },
    {
      "name": "Metadata",
      "description": "Dataset manifest and publication metadata."
    }
  ],
  "paths": {
    "/courses": {
      "get": {
        "tags": [
          "Courses"
        ],
        "summary": "List courses",
        "description": "Returns public disc golf course records. Use country and region filters where available, and limit and offset for pagination.",
        "operationId": "listCourses",
        "parameters": [
          {
            "$ref": "#/components/parameters/Country"
          },
          {
            "$ref": "#/components/parameters/Region"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "description": "A course list response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseListResponse"
                },
                "examples": {
                  "gbList": {
                    "summary": "One Great Britain course",
                    "value": {
                      "schema_version": "1.0.0",
                      "generated_at": "2026-05-03T18:02:34.285060Z",
                      "attribution": "Course data supplied by DiscGolfAPI.",
                      "attribution_required": true,
                      "license": "Free to use with attribution.",
                      "license_url": "https://discgolfapi.com/licence/",
                      "terms_url": "https://discgolfapi.com/terms/",
                      "no_warranty": "Data is provided as-is. Accuracy, completeness and availability are not guaranteed.",
                      "meta": {
                        "publish_version": "20260503T180234Z",
                        "release_scope": "all"
                      },
                      "count": 1,
                      "total": 30,
                      "offset": 0,
                      "courses": [
                        {
                          "id": "crs_ackers_adventure",
                          "slug": "ackers-adventure",
                          "name": "Ackers Adventure",
                          "lat": 52.4579339107501,
                          "lon": -1.85255245845531,
                          "country_code": "GB",
                          "region_code": "ENG",
                          "locality": null,
                          "website": "https://ackers-adventure.co.uk/",
                          "operator_name": null,
                          "holes": null,
                          "existence_status": "existing",
                          "operational_status": "open",
                          "access_model": "unknown",
                          "condition_status": "unknown",
                          "listing_status": "listed",
                          "confidence_score": 0.65,
                          "verification_strength": "medium",
                          "last_verified_at": "2026-04-27T22:40:53.197339Z",
                          "updated_at": "2026-04-29T20:35:03.752326Z",
                          "primary_layout": {
                            "id": "c30f319b-b111-4b94-afd1-77585659f6f2",
                            "slug": "main-layout",
                            "name": "Main Layout",
                            "holes": null,
                            "par_total": null,
                            "length_meters": null,
                            "confidence_score": 0.65,
                            "verification_strength": "medium",
                            "last_verified_at": "2026-04-27T22:40:53.197339Z"
                          },
                          "attributes": null
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/courses/{id}": {
      "get": {
        "tags": [
          "Courses"
        ],
        "summary": "Get a course by ID",
        "description": "Returns a single course by stable public course ID. The response uses the same envelope and courses array shape as the list endpoint.",
        "operationId": "getCourse",
        "parameters": [
          {
            "$ref": "#/components/parameters/CourseId"
          }
        ],
        "responses": {
          "200": {
            "description": "A single course response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/countries": {
      "get": {
        "tags": [
          "Countries"
        ],
        "summary": "List countries",
        "description": "Returns countries represented in the current public dataset.",
        "operationId": "listCountries",
        "responses": {
          "200": {
            "description": "A country coverage response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CountriesResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/regions": {
      "get": {
        "tags": [
          "Regions"
        ],
        "summary": "List regions",
        "description": "Returns regions, states and subdivisions represented in the current public dataset where region codes are available.",
        "operationId": "listRegions",
        "responses": {
          "200": {
            "description": "A region coverage response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegionsResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/updates/recent": {
      "get": {
        "tags": [
          "Updates"
        ],
        "summary": "List recent updates",
        "description": "Returns recent course additions or changes in the public dataset.",
        "operationId": "listRecentUpdates",
        "responses": {
          "200": {
            "description": "A recent updates response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecentUpdatesResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/manifest.json": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "summary": "Get dataset manifest",
        "description": "Returns the current public dataset manifest, including contract version, publish version, generated timestamp, dataset counts and published JSON artefact metadata.",
        "operationId": "getManifest",
        "servers": [
          {
            "url": "https://io.discgolfapi.com",
            "description": "Production API metadata root"
          }
        ],
        "responses": {
          "200": {
            "description": "The current dataset manifest.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Manifest"
                },
                "examples": {
                  "manifest": {
                    "summary": "Manifest excerpt",
                    "value": {
                      "contract_version": "1.0.0",
                      "publish_version": "20260503T180234Z",
                      "generated_at": "2026-05-03T18:02:34.285060Z",
                      "release_scope": "all",
                      "artefact_base_url": "https://io.discgolfapi.com/v1/20260503T180234Z/",
                      "counts": {
                        "courses": 727,
                        "countries": 23,
                        "regions": 31,
                        "recent_updates": 100
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "CourseId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Stable public course ID.",
        "schema": {
          "type": "string",
          "pattern": "^crs_[a-z0-9_]+$",
          "examples": [
            "crs_ackers_adventure"
          ]
        }
      },
      "Country": {
        "name": "country",
        "in": "query",
        "required": false,
        "description": "Country code filter.",
        "schema": {
          "type": "string",
          "minLength": 2,
          "maxLength": 2,
          "examples": [
            "GB"
          ]
        }
      },
      "Region": {
        "name": "region",
        "in": "query",
        "required": false,
        "description": "Region, state or subdivision code filter where supported.",
        "schema": {
          "type": "string",
          "examples": [
            "ENG"
          ]
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Maximum number of records to return. Must be a positive integer.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "examples": [
            5
          ]
        }
      },
      "Offset": {
        "name": "offset",
        "in": "query",
        "required": false,
        "description": "Number of matching records to skip before returning the current page.",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "default": 0,
          "examples": [
            0
          ]
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "examples": {
              "courseNotFound": {
                "value": {
                  "error": "Course not found"
                }
              }
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Too many requests.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "ServerError": {
        "description": "Temporary server error.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "schemas": {
      "CommonEnvelope": {
        "type": "object",
        "properties": {
          "schema_version": {
            "type": "string"
          },
          "generated_at": {
            "type": "string",
            "format": "date-time"
          },
          "attribution": {
            "type": "string"
          },
          "attribution_required": {
            "type": "boolean"
          },
          "license": {
            "type": "string"
          },
          "license_url": {
            "type": "string",
            "format": "uri"
          },
          "terms_url": {
            "type": "string",
            "format": "uri"
          },
          "no_warranty": {
            "type": "string"
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMeta"
          }
        },
        "required": [
          "schema_version",
          "generated_at",
          "attribution",
          "attribution_required",
          "license",
          "license_url",
          "terms_url",
          "no_warranty"
        ]
      },
      "ResponseMeta": {
        "type": "object",
        "properties": {
          "publish_version": {
            "type": "string"
          },
          "release_scope": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "PaginationMeta": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer",
            "minimum": 0
          },
          "total": {
            "type": "integer",
            "minimum": 0
          },
          "offset": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "count"
        ]
      },
      "CourseListResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CommonEnvelope"
          },
          {
            "$ref": "#/components/schemas/PaginationMeta"
          },
          {
            "type": "object",
            "properties": {
              "courses": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Course"
                }
              }
            },
            "required": [
              "courses"
            ]
          }
        ]
      },
      "CourseResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CommonEnvelope"
          },
          {
            "$ref": "#/components/schemas/PaginationMeta"
          },
          {
            "type": "object",
            "properties": {
              "courses": {
                "type": "array",
                "minItems": 1,
                "maxItems": 1,
                "items": {
                  "$ref": "#/components/schemas/Course"
                }
              }
            },
            "required": [
              "courses"
            ]
          }
        ]
      },
      "Course": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "lat": {
            "type": [
              "number",
              "null"
            ]
          },
          "lon": {
            "type": [
              "number",
              "null"
            ]
          },
          "country_code": {
            "type": "string"
          },
          "region_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "locality": {
            "type": [
              "string",
              "null"
            ]
          },
          "website": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "operator_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "holes": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "description": "Known hole count. Null means unknown, not zero."
          },
          "existence_status": {
            "type": "string"
          },
          "operational_status": {
            "type": "string"
          },
          "access_model": {
            "type": "string"
          },
          "condition_status": {
            "type": "string"
          },
          "listing_status": {
            "type": "string"
          },
          "confidence_score": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "verification_strength": {
            "type": "string"
          },
          "last_verified_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "primary_layout": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PrimaryLayout"
              },
              {
                "type": "null"
              }
            ]
          },
          "attributes": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          }
        },
        "required": [
          "id",
          "slug",
          "name",
          "lat",
          "lon",
          "country_code",
          "region_code",
          "locality",
          "website",
          "operator_name",
          "holes",
          "existence_status",
          "operational_status",
          "access_model",
          "condition_status",
          "listing_status",
          "confidence_score",
          "verification_strength",
          "last_verified_at",
          "updated_at",
          "primary_layout",
          "attributes"
        ]
      },
      "PrimaryLayout": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "holes": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1
          },
          "par_total": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1
          },
          "length_meters": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0
          },
          "confidence_score": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "verification_strength": {
            "type": "string"
          },
          "last_verified_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "slug",
          "name",
          "holes",
          "par_total",
          "length_meters",
          "confidence_score",
          "verification_strength",
          "last_verified_at"
        ]
      },
      "Country": {
        "type": "object",
        "properties": {
          "country_code": {
            "type": "string"
          },
          "country_name": {
            "type": "string"
          },
          "course_count": {
            "type": "integer",
            "minimum": 0
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "country_code",
          "country_name",
          "course_count",
          "updated_at"
        ]
      },
      "CountriesResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CommonEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "countries": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Country"
                }
              }
            },
            "required": [
              "countries"
            ]
          }
        ]
      },
      "Region": {
        "type": "object",
        "properties": {
          "country_code": {
            "type": "string"
          },
          "region_code": {
            "type": "string"
          },
          "region_name": {
            "type": "string"
          },
          "course_count": {
            "type": "integer",
            "minimum": 0
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "country_code",
          "region_code",
          "region_name",
          "course_count",
          "updated_at"
        ]
      },
      "RegionsResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CommonEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "regions": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Region"
                }
              }
            },
            "required": [
              "regions"
            ]
          }
        ]
      },
      "RecentUpdate": {
        "type": "object",
        "properties": {
          "course_id": {
            "type": "string"
          },
          "course_slug": {
            "type": "string"
          },
          "course_name": {
            "type": "string"
          },
          "change_type": {
            "type": "string"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "course_id",
          "course_slug",
          "course_name",
          "change_type",
          "updated_at"
        ]
      },
      "RecentUpdatesResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CommonEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "updates": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/RecentUpdate"
                }
              }
            },
            "required": [
              "updates"
            ]
          }
        ]
      },
      "Manifest": {
        "type": "object",
        "properties": {
          "contract_version": {
            "type": "string"
          },
          "publish_version": {
            "type": "string"
          },
          "generated_at": {
            "type": "string",
            "format": "date-time"
          },
          "release_scope": {
            "type": "string"
          },
          "artefact_base_url": {
            "type": "string",
            "format": "uri"
          },
          "counts": {
            "type": "object",
            "properties": {
              "courses": {
                "type": "integer",
                "minimum": 0
              },
              "countries": {
                "type": "integer",
                "minimum": 0
              },
              "regions": {
                "type": "integer",
                "minimum": 0
              },
              "recent_updates": {
                "type": "integer",
                "minimum": 0
              }
            },
            "required": [
              "courses",
              "countries",
              "regions",
              "recent_updates"
            ]
          },
          "artefacts": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "sha256": {
                  "type": "string"
                },
                "bytes": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "required": [
                "path",
                "sha256",
                "bytes"
              ]
            }
          }
        },
        "required": [
          "contract_version",
          "publish_version",
          "generated_at",
          "release_scope",
          "artefact_base_url",
          "counts",
          "artefacts"
        ]
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      }
    }
  }
}
