{
  "openapi": "3.1.0",
  "info": {
    "title": "Ganadería JS Beefmasters",
    "description": "Public content surface for Ganadería JS Beefmasters, a family Beefmaster cattle ranch in Pérez Zeledón, Costa Rica. This is a marketing/informational site, not a transactional API — the documented resources below are read-only content endpoints with content negotiation for agents and crawlers.\n\nVersioning and deprecation policy: the versioned content surface (GET /, /{unknownPath} 404s, /sitemap.xml, /llms.txt) is versioned via the `API-Version` response header (current value: \"1\"), sent on every response on those paths, including error responses. `/developers` (documentation) and this file, `/openapi.json` (the spec describing the surface), are meta-resources and intentionally excluded from versioning — there's nothing for a consumer to pin against there. There is no URL path versioning because there is only one resource family (content, not transactional data) and no history of breaking changes. If a breaking change is ever made, the version will change and, for at least 90 days beforehand, responses on the outgoing version will include a `Sunset` header (RFC 8594) naming the cutover date. This policy is formalized machine-readably in `info.x-versioning-policy` below.\n\nRate limiting: GET /, /sitemap.xml, /llms.txt, and any 404 are limited to 120 requests per 60-second window per client IP, tracked in a fixed window. Every response on those paths carries `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset` (seconds until the window resets), per the conventions in draft-ietf-httpapi-ratelimit-headers. A request over the limit gets `429` plus `Retry-After` (seconds), content-negotiated the same as other responses. No authentication is required — the limit is per IP, not per key — and no API key exists to raise it; see /developers.",
    "version": "1.0.0",
    "contact": {
      "name": "Ganadería JS Beefmasters",
      "email": "ganaderiajsbeefmasters@gmail.com",
      "url": "https://ganaderiajsbeefmasters.com"
    },
    "x-versioning-policy": {
      "scheme": "header",
      "header": "API-Version",
      "current": "1",
      "appliesTo": ["/", "/{unknownPath}", "/sitemap.xml", "/llms.txt"],
      "excludedResources": {
        "paths": ["/developers", "/openapi.json"],
        "reason": "Documentation and the spec itself are meta-resources, not versioned content."
      },
      "breakingChangePolicy": "API_VERSION increments on any breaking change to response shape (JSON error format, Markdown structure).",
      "deprecation": {
        "signal": "header",
        "header": "Sunset",
        "rfc": "RFC 8594",
        "rfcUrl": "https://www.rfc-editor.org/rfc/rfc8594",
        "minimumNoticeDays": 90,
        "activeToday": false
      },
      "humanReadablePolicy": "https://ganaderiajsbeefmasters.com/developers"
    },
    "x-cli": {
      "name": "ganaderiajs",
      "package": "https://www.npmjs.com/package/ganaderiajs",
      "install": "npm install -g ganaderiajs",
      "run": "npx ganaderiajs",
      "description": "Official zero-dependency CLI wrapping this API: content negotiation (get), the sitemap and llms.txt, and RateLimit-*/API-Version inspection (status) for self-throttling scripts."
    }
  },
  "servers": [
    { "url": "https://ganaderiajsbeefmasters.com" }
  ],
  "paths": {
    "/": {
      "get": {
        "operationId": "getHomepage",
        "summary": "Get the homepage",
        "description": "Returns the homepage content. Supports content negotiation via the Accept header: request text/markdown for a clean Markdown rendition of the same content, or text/html (the default) for the full rendered page.",
        "parameters": [
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": ["text/html", "text/markdown"],
              "default": "text/html"
            },
            "description": "Set to text/markdown to receive a Markdown document instead of HTML."
          }
        ],
        "responses": {
          "200": {
            "description": "Homepage content in the negotiated format.",
            "headers": {
              "Vary": {
                "description": "Always 'Accept' — the response body and Content-Type depend on the request's Accept header.",
                "schema": { "type": "string" }
              },
              "API-Version": { "$ref": "#/components/headers/APIVersion" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "text/html": {
                "schema": { "type": "string" }
              },
              "text/markdown": {
                "schema": { "type": "string" }
              }
            }
          },
          "405": {
            "description": "Method not allowed — only GET and HEAD are supported on this resource.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/{unknownPath}": {
      "get": {
        "operationId": "getUnknownPath",
        "summary": "Any path with no matching resource",
        "description": "This site has one page. Any other path returns a real HTTP 404, content-negotiated the same way as the homepage: text/markdown, text/html, or application/json (default).",
        "parameters": [
          {
            "name": "unknownPath",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "404": {
            "description": "No resource exists at this path.",
            "headers": {
              "Vary": { "schema": { "type": "string" } },
              "API-Version": { "$ref": "#/components/headers/APIVersion" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No resource exists at /this-path.",
                    "resolution": "See / for the homepage, /sitemap.xml for all indexed pages, or /llms.txt for a machine-readable index of this site's resources."
                  }
                }
              },
              "text/markdown": { "schema": { "type": "string" } },
              "text/html": { "schema": { "type": "string" } }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/sitemap.xml": {
      "get": {
        "operationId": "getSitemap",
        "summary": "Get the XML sitemap",
        "responses": {
          "200": {
            "description": "Sitemap in the sitemaps.org 0.9 schema.",
            "headers": {
              "API-Version": { "$ref": "#/components/headers/APIVersion" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": { "application/xml": { "schema": { "type": "string" } } }
          },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsTxt",
        "summary": "Get the llms.txt index for AI agents",
        "responses": {
          "200": {
            "description": "Plain-text/Markdown index describing the site for LLMs and agents.",
            "headers": {
              "API-Version": { "$ref": "#/components/headers/APIVersion" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": { "text/markdown": { "schema": { "type": "string" } } }
          },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/developers": {
      "get": {
        "operationId": "getDeveloperPortal",
        "summary": "Get the developer portal",
        "description": "Human-readable API documentation: authentication (none), rate limits, versioning policy, and a quickstart. Always returns text/html regardless of Accept.",
        "responses": {
          "200": {
            "description": "Developer portal page.",
            "content": { "text/html": { "schema": { "type": "string" } } }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": {
                "type": "string",
                "enum": ["method_not_allowed", "not_found", "rate_limited"]
              },
              "message": { "type": "string" },
              "resolution": { "type": "string" }
            }
          }
        }
      }
    },
    "headers": {
      "APIVersion": {
        "description": "The API version that generated this response. See the versioning and deprecation policy in the top-level description.",
        "schema": { "type": "string", "example": "1" }
      },
      "RateLimitLimit": {
        "description": "The request quota for the current 60-second window, per client IP.",
        "schema": { "type": "integer", "example": 120 }
      },
      "RateLimitRemaining": {
        "description": "Requests remaining in the current window for this client IP.",
        "schema": { "type": "integer", "example": 119 }
      },
      "RateLimitReset": {
        "description": "Seconds until the current window resets and the quota refills.",
        "schema": { "type": "integer", "example": 42 }
      },
      "Sunset": {
        "description": "RFC 8594 cutover date for a deprecated API version, given at least 90 days' notice. Not currently sent on any response — API-Version 1 has no scheduled deprecation (see info.x-versioning-policy.deprecation.activeToday). Defined here so its shape is documented in advance of ever being used.",
        "schema": { "type": "string", "format": "date-time", "example": "2027-01-01T00:00:00Z" }
      }
    },
    "responses": {
      "RateLimited": {
        "description": "Rate limit exceeded for this client IP on this path.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying — always equal to RateLimit-Reset.",
            "schema": { "type": "integer" }
          },
          "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
          "RateLimit-Remaining": {
            "description": "Always 0 on a 429.",
            "schema": { "type": "integer", "example": 0 }
          },
          "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" },
          "API-Version": { "$ref": "#/components/headers/APIVersion" }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "error": {
                "code": "rate_limited",
                "message": "Rate limit exceeded for /.",
                "resolution": "This IP has exceeded the request limit for this window. Wait for Retry-After seconds, then retry. See /developers for the documented limit."
              }
            }
          },
          "text/markdown": { "schema": { "type": "string" } },
          "text/html": { "schema": { "type": "string" } }
        }
      }
    }
  }
}
