{
  "openapi": "3.0.3",
  "info": {
    "title": "TN Fishing Public API (fishing.calvinllc.net)",
    "description": "TN Fishing public read APIs for Tennessee fishing spot scores, daily bites, access points, map timeline, community stats, and health on https://fishing.calvinllc.net. Current version is v1. Paths under /api/v1/ are preferred; unversioned /api/* aliases remain supported. Breaking changes ship as /api/v2 with >=90 days overlap. Deprecated operations advertise Deprecation and Sunset headers (RFC 8594). Optional header X-API-Version: 1 selects v1 when path is unversioned. Rate limits default to 120 requests/minute per client (RATE_LIMIT_RPM). Successful responses include RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset. HTTP 429 includes Retry-After. API errors use {\"error\":{\"code\",\"message\",\"hint?\"}}.",
    "version": "1.0.0",
    "contact": {
      "name": "Calvin LLC",
      "email": "calvinguzman4@gmail.com",
      "url": "https://fishing.calvinllc.net/contact"
    },
    "license": {
      "name": "Proprietary"
    },
    "x-sunset-policy": {
      "current": "v1",
      "overlapDays": 90,
      "headers": [
        "Deprecation",
        "Sunset",
        "Link"
      ]
    }
  },
  "servers": [
    {
      "url": "https://fishing.calvinllc.net",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Spots",
      "description": "Scored fishing locations"
    },
    {
      "name": "Scores",
      "description": "Daily bites and planner-oriented scores"
    },
    {
      "name": "Map",
      "description": "Map and density layers"
    },
    {
      "name": "Meta",
      "description": "Health and discovery"
    }
  ],
  "paths": {
    "/healthz": {
      "get": {
        "operationId": "getHealthz",
        "tags": [
          "Meta"
        ],
        "summary": "Liveness probe",
        "description": "Returns plain-text ok when the web tier is up.",
        "responses": {
          "200": {
            "description": "Service is healthy",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "example": "ok"
                }
              }
            }
          }
        }
      }
    },
    "/api/spots": {
      "get": {
        "operationId": "listSpots",
        "tags": [
          "Spots"
        ],
        "summary": "List scored Tennessee spots",
        "description": "Returns the watched spot catalog with latest composite score, rating, confidence, and best species.",
        "parameters": [
          {
            "name": "simple",
            "in": "query",
            "required": false,
            "description": "If set to 1, request a lighter payload when supported upstream.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Array of spot score objects",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Max requests per window"
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Remaining requests in window"
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until window reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SpotScore"
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/today": {
      "get": {
        "operationId": "listTodayBites",
        "tags": [
          "Scores"
        ],
        "summary": "Daily best bites",
        "description": "Ranked species/spot pairs for the current local fishing day.",
        "responses": {
          "200": {
            "description": "Best bite list",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Bite"
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ]
      }
    },
    "/api/access-points": {
      "get": {
        "operationId": "listAccessPoints",
        "tags": [
          "Map"
        ],
        "summary": "Boat ramps and access points",
        "description": "Public access points linked to watched waters.",
        "responses": {
          "200": {
            "description": "Access point list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AccessPoint"
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ]
      }
    },
    "/api/catch-density": {
      "get": {
        "operationId": "getCatchDensity",
        "tags": [
          "Map"
        ],
        "summary": "Aggregated catch density",
        "description": "De-identified catch density cells for map overlays.",
        "responses": {
          "200": {
            "description": "Density payload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatchDensity"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ]
      }
    },
    "/api/map/timeline": {
      "get": {
        "operationId": "getMapTimeline",
        "tags": [
          "Map"
        ],
        "summary": "Map score timeline",
        "description": "Time-indexed score frames for animated map views.",
        "parameters": [
          {
            "name": "hours",
            "in": "query",
            "required": false,
            "description": "Optional lookback window in hours",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 168
            }
          },
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Timeline frames",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MapTimeline"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/stats/community": {
      "get": {
        "operationId": "getCommunityStats",
        "tags": [
          "Scores"
        ],
        "summary": "Community statistics",
        "description": "Aggregate community catch and activity stats.",
        "responses": {
          "200": {
            "description": "Stats object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommunityStats"
                }
              }
            }
          },
          "502": {
            "description": "Upstream fishd unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ]
      }
    },
    "/api/bathymetry/{slug}": {
      "get": {
        "operationId": "getBathymetry",
        "tags": [
          "Map"
        ],
        "summary": "Bathymetry for a spot",
        "description": "Depth / bathymetry metadata for a spot slug when available.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Spot slug (lowercase kebab-case)",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          },
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Bathymetry payload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Bathymetry"
                }
              }
            }
          },
          "400": {
            "description": "Invalid slug",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApiJson",
        "tags": [
          "Meta"
        ],
        "summary": "OpenAPI document (JSON)",
        "description": "This OpenAPI 3 document.",
        "responses": {
          "200": {
            "description": "OpenAPI 3 JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiDocument"
                }
              }
            }
          }
        }
      }
    },
    "/api/openapi.yaml": {
      "get": {
        "operationId": "getOpenApiYaml",
        "tags": [
          "Meta"
        ],
        "summary": "OpenAPI document (YAML)",
        "description": "YAML encoding of the public OpenAPI document.",
        "responses": {
          "200": {
            "description": "OpenAPI 3 YAML",
            "content": {
              "application/yaml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsTxt",
        "tags": [
          "Meta"
        ],
        "summary": "LLM/agent discovery file",
        "description": "Plain-text discovery document including when-to-use guidance.",
        "responses": {
          "200": {
            "description": "llms.txt body",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/spots": {
      "get": {
        "operationId": "listSpotsV1",
        "tags": [
          "Spots"
        ],
        "summary": "List scored Tennessee spots",
        "description": "Returns the watched spot catalog with latest composite score, rating, confidence, and best species. Versioned v1 alias of /api/spots.",
        "parameters": [
          {
            "name": "simple",
            "in": "query",
            "required": false,
            "description": "If set to 1, request a lighter payload when supported upstream.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Array of spot score objects",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Max requests per window"
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Remaining requests in window"
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until window reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SpotScore"
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-api-version": "1"
      }
    },
    "/api/v1/today": {
      "get": {
        "operationId": "listTodayBitesV1",
        "tags": [
          "Scores"
        ],
        "summary": "Daily best bites",
        "description": "Ranked species/spot pairs for the current local fishing day. Versioned v1 alias of /api/today.",
        "responses": {
          "200": {
            "description": "Best bite list",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Bite"
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-api-version": "1"
      }
    },
    "/api/v1/access-points": {
      "get": {
        "operationId": "listAccessPointsV1",
        "tags": [
          "Map"
        ],
        "summary": "Boat ramps and access points",
        "description": "Public access points linked to watched waters. Versioned v1 alias of /api/access-points.",
        "responses": {
          "200": {
            "description": "Access point list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AccessPoint"
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-api-version": "1"
      }
    },
    "/api/v1/catch-density": {
      "get": {
        "operationId": "getCatchDensityV1",
        "tags": [
          "Map"
        ],
        "summary": "Aggregated catch density",
        "description": "De-identified catch density cells for map overlays. Versioned v1 alias of /api/catch-density.",
        "responses": {
          "200": {
            "description": "Density payload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatchDensity"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-api-version": "1"
      }
    },
    "/api/v1/map/timeline": {
      "get": {
        "operationId": "getMapTimelineV1",
        "tags": [
          "Map"
        ],
        "summary": "Map score timeline",
        "description": "Time-indexed score frames for animated map views. Versioned v1 alias of /api/map/timeline.",
        "parameters": [
          {
            "name": "hours",
            "in": "query",
            "required": false,
            "description": "Optional lookback window in hours",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 168
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Timeline frames",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MapTimeline"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-api-version": "1"
      }
    },
    "/api/v1/stats/community": {
      "get": {
        "operationId": "getCommunityStatsV1",
        "tags": [
          "Scores"
        ],
        "summary": "Community statistics",
        "description": "Aggregate community catch and activity stats. Versioned v1 alias of /api/stats/community.",
        "responses": {
          "200": {
            "description": "Stats object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommunityStats"
                }
              }
            }
          },
          "502": {
            "description": "Upstream fishd unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-api-version": "1"
      }
    },
    "/api/v1/bathymetry/{slug}": {
      "get": {
        "operationId": "getBathymetryV1",
        "tags": [
          "Map"
        ],
        "summary": "Bathymetry for a spot",
        "description": "Depth / bathymetry metadata for a spot slug when available. Versioned v1 alias of /api/bathymetry/{slug}.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Spot slug (lowercase kebab-case)",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bathymetry payload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Bathymetry"
                }
              }
            }
          },
          "400": {
            "description": "Invalid slug",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-api-version": "1"
      }
    }
  },
  "components": {
    "schemas": {
      "SpotScore": {
        "type": "object",
        "required": [
          "slug",
          "name"
        ],
        "properties": {
          "slug": {
            "type": "string",
            "description": "Stable spot identifier"
          },
          "name": {
            "type": "string"
          },
          "water_type": {
            "type": "string"
          },
          "county": {
            "type": "string"
          },
          "lat": {
            "type": "number",
            "format": "float"
          },
          "lon": {
            "type": "number",
            "format": "float"
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "rating": {
            "type": "string",
            "enum": [
              "excellent",
              "good",
              "fair",
              "poor",
              "skip"
            ]
          },
          "confidence": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "best_species": {
            "type": "string",
            "nullable": true
          },
          "missing_inputs": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Bite": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "species": {
            "type": "string"
          },
          "score": {
            "type": "integer"
          },
          "rating": {
            "type": "string"
          },
          "best_window": {
            "type": "string"
          }
        }
      },
      "AccessPoint": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "lat": {
            "type": "number"
          },
          "lon": {
            "type": "number"
          },
          "slug": {
            "type": "string",
            "description": "Related spot slug when known"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorDetail"
          }
        }
      },
      "ErrorDetail": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "Stable machine-readable error code"
          },
          "message": {
            "type": "string"
          },
          "hint": {
            "type": "string",
            "description": "Optional remediation hint"
          }
        }
      },
      "CatchDensity": {
        "type": "object",
        "properties": {
          "cells": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "lat": {
                  "type": "number"
                },
                "lon": {
                  "type": "number"
                },
                "count": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            }
          },
          "updated_at": {
            "type": "string"
          }
        }
      },
      "MapTimeline": {
        "type": "object",
        "properties": {
          "frames": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "ts": {
                  "type": "integer"
                },
                "scores": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "integer"
                  }
                }
              }
            }
          },
          "hours": {
            "type": "integer"
          }
        }
      },
      "CommunityStats": {
        "type": "object",
        "properties": {
          "catches": {
            "type": "integer"
          },
          "anglers": {
            "type": "integer"
          },
          "spots_logged": {
            "type": "integer"
          }
        },
        "additionalProperties": true
      },
      "Bathymetry": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "available": {
            "type": "boolean"
          },
          "source": {
            "type": "string"
          },
          "contours": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "OpenApiDocument": {
        "type": "object",
        "required": [
          "openapi",
          "info",
          "paths"
        ],
        "properties": {
          "openapi": {
            "type": "string"
          },
          "info": {
            "type": "object",
            "additionalProperties": true
          },
          "paths": {
            "type": "object",
            "additionalProperties": true
          },
          "components": {
            "type": "object",
            "additionalProperties": true
          }
        }
      }
    },
    "responses": {
      "TooManyRequests": {
        "description": "Rate limit exceeded",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Limit": {
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Reset": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "text/plain": {
            "schema": {
              "type": "string",
              "example": "rate limit exceeded\n"
            }
          },
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "parameters": {
      "ApiVersionHeader": {
        "name": "X-API-Version",
        "in": "header",
        "required": false,
        "description": "Optional API version when using unversioned /api paths. Prefer /api/v1 path prefixes.",
        "schema": {
          "type": "string",
          "enum": [
            "1"
          ]
        }
      }
    }
  }
}
