{
  "openapi": "3.1.0",
  "info": {
    "title": "Hamlet's Friends public HTTP resources",
    "version": "1.0.0",
    "summary": "What software can read on hamletsfriends.com, and the link that opens the booking engine on given dates.",
    "description": "Hamlet's Friends is a rural coliving and coworking community near Madrid. Every operation here is a GET. Only internet status needs authorization. Notes for people, including rate limits and every error code, are at https://hamletsfriends.com/developers/.",
    "contact": {
      "name": "Hamlet's Friends",
      "email": "hola@hamletsfriends.com",
      "url": "https://hamletsfriends.com/developers/"
    }
  },
  "servers": [
    {
      "url": "https://hamletsfriends.com"
    }
  ],
  "externalDocs": {
    "description": "Authentication, rate limits and errors",
    "url": "https://hamletsfriends.com/developers/"
  },
  "tags": [
    {
      "name": "Booking",
      "description": "Hand a person a link into the booking engine."
    },
    {
      "name": "Text resources",
      "description": "Plain-text and Markdown files for agents."
    },
    {
      "name": "Markdown twins",
      "description": "Markdown versions of the indexed pages."
    },
    {
      "name": "Internet status",
      "description": "Live state of the finca's internet and Wi-Fi."
    }
  ],
  "paths": {
    "/book-now/": {
      "get": {
        "operationId": "openBookingSearch",
        "tags": [
          "Booking"
        ],
        "summary": "Open the booking engine on given dates",
        "description": "Returns the booking page. The Sirvoy booking widget on it reads check_in, check_out and adults from the URL and shows the rooms and cabins free on those dates, with their prices. Without the parameters it shows an empty search. Availability and prices come from Sirvoy when the page loads, and this site publishes neither in machine-readable form. Hand the URL to the person who wants to book.",
        "parameters": [
          {
            "name": "check_in",
            "in": "query",
            "required": false,
            "description": "Arrival date, YYYY-MM-DD.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2027-03-08"
          },
          {
            "name": "check_out",
            "in": "query",
            "required": false,
            "description": "Departure date, YYYY-MM-DD, after check_in.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2027-03-12"
          },
          {
            "name": "adults",
            "in": "query",
            "required": false,
            "description": "Number of adults.",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "example": 2
          }
        ],
        "responses": {
          "200": {
            "description": "The booking page.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/pricing.md": {
      "get": {
        "operationId": "getPricing",
        "tags": [
          "Text resources"
        ],
        "summary": "Every published rate, as Markdown",
        "description": "Generated at build time from the rates the /pricing/ page shows.",
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Your copy is current: you sent If-None-Match with the ETag of this version."
          }
        }
      }
    },
    "/facts.md": {
      "get": {
        "operationId": "getFacts",
        "tags": [
          "Text resources"
        ],
        "summary": "Key facts about Hamlet's Friends, as Markdown",
        "description": "Location, distances, units, check-in times, policies and contact details in one file.",
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Your copy is current: you sent If-None-Match with the ETag of this version."
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsIndex",
        "tags": [
          "Text resources"
        ],
        "summary": "Site index for language models",
        "description": "An llmstxt.org index with a one-line description of every important page.",
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Your copy is current: you sent If-None-Match with the ETag of this version."
          }
        }
      }
    },
    "/llms-full.txt": {
      "get": {
        "operationId": "getLlmsFull",
        "tags": [
          "Text resources"
        ],
        "summary": "Full text of the core pages",
        "description": "The Markdown twins of the core English pages, joined into one file. Range requests work.",
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Your copy is current: you sent If-None-Match with the ETag of this version."
          }
        }
      }
    },
    "/sitemap.xml": {
      "get": {
        "operationId": "getSitemap",
        "tags": [
          "Text resources"
        ],
        "summary": "Every indexed URL",
        "description": "Each indexed URL with its last change date and its language alternates.",
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "text/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Your copy is current: you sent If-None-Match with the ETag of this version."
          }
        }
      }
    },
    "/{page}/index.md": {
      "get": {
        "operationId": "getPageMarkdown",
        "tags": [
          "Markdown twins"
        ],
        "summary": "Markdown twin of an English page",
        "description": "The Markdown version of an indexed page, with front matter that names its title, canonical URL, language and alternates. Pages kept out of search have no twin.",
        "parameters": [
          {
            "name": "page",
            "in": "path",
            "required": true,
            "description": "The page path segment, as in https://hamletsfriends.com/coliving/.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            },
            "examples": {
              "coliving": {
                "value": "coliving"
              },
              "coworking": {
                "value": "coworking"
              },
              "pricing": {
                "value": "pricing"
              },
              "faqs": {
                "value": "faqs"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The Markdown twin.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Your copy is current: you sent If-None-Match with the ETag of this version."
          },
          "404": {
            "description": "No such page, or a page kept out of search.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/{locale}/{page}/index.md": {
      "get": {
        "operationId": "getLocalizedPageMarkdown",
        "tags": [
          "Markdown twins"
        ],
        "summary": "Markdown twin of a Spanish or German page",
        "description": "The Markdown version of an indexed page, with front matter that names its title, canonical URL, language and alternates. Pages kept out of search have no twin.",
        "parameters": [
          {
            "name": "locale",
            "in": "path",
            "required": true,
            "description": "es or de.",
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "de"
              ]
            }
          },
          {
            "name": "page",
            "in": "path",
            "required": true,
            "description": "The page path segment, as in https://hamletsfriends.com/coliving/.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            },
            "examples": {
              "coliving": {
                "value": "coliving"
              },
              "coworking": {
                "value": "coworking"
              },
              "pricing": {
                "value": "pricing"
              },
              "faqs": {
                "value": "faqs"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The Markdown twin.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Your copy is current: you sent If-None-Match with the ETag of this version."
          },
          "404": {
            "description": "No such page, or a page kept out of search.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/blog/{slug}/index.md": {
      "get": {
        "operationId": "getBlogPostMarkdown",
        "tags": [
          "Markdown twins"
        ],
        "summary": "Markdown twin of a blog post",
        "description": "The Markdown version of an indexed page, with front matter that names its title, canonical URL, language and alternates. Pages kept out of search have no twin.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The post slug, as in https://hamletsfriends.com/blog/workation-in-spain/.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            },
            "examples": {
              "workation": {
                "value": "workation-in-spain"
              },
              "cost": {
                "value": "rural-coliving-spain-cost"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The Markdown twin.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Your copy is current: you sent If-None-Match with the ETag of this version."
          },
          "404": {
            "description": "No such page, or a page kept out of search.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/internet-status": {
      "get": {
        "operationId": "getInternetStatus",
        "tags": [
          "Internet status"
        ],
        "summary": "Live internet and Wi-Fi status at the finca",
        "description": "Requests from the finca's own network need no password. From anywhere else, send the guest Wi-Fi password in the x-status-password header. The snapshot refreshes when it is more than five minutes old, so polling faster returns the same data.",
        "security": [
          {},
          {
            "statusPassword": []
          }
        ],
        "responses": {
          "200": {
            "description": "The current snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "updatedAt",
                    "internet",
                    "spots",
                    "override"
                  ],
                  "properties": {
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When this snapshot was taken."
                    },
                    "internet": {
                      "type": "object",
                      "required": [
                        "level",
                        "attribution"
                      ],
                      "properties": {
                        "level": {
                          "type": "string",
                          "enum": [
                            "ok",
                            "degraded",
                            "outage"
                          ]
                        },
                        "attribution": {
                          "type": "string",
                          "enum": [
                            "ok",
                            "provider",
                            "us"
                          ],
                          "description": "Who is responsible when the level is not ok: the internet provider, or the finca's own equipment."
                        }
                      }
                    },
                    "spots": {
                      "type": "array",
                      "description": "Wi-Fi in each area: the coworking space, the grounds with the cabins, and the Molino.",
                      "items": {
                        "type": "object",
                        "required": [
                          "key",
                          "level",
                          "showAps",
                          "aps"
                        ],
                        "properties": {
                          "key": {
                            "type": "string",
                            "enum": [
                              "coworking",
                              "terrain",
                              "molino"
                            ]
                          },
                          "level": {
                            "type": "string",
                            "enum": [
                              "ok",
                              "limited",
                              "down"
                            ]
                          },
                          "showAps": {
                            "type": "boolean",
                            "description": "Whether this area lists its access points."
                          },
                          "aps": {
                            "type": "array",
                            "description": "Access points that are limited or down. Empty when all are fine.",
                            "items": {
                              "type": "object",
                              "required": [
                                "key",
                                "level"
                              ],
                              "properties": {
                                "key": {
                                  "type": "string"
                                },
                                "level": {
                                  "type": "string",
                                  "enum": [
                                    "limited",
                                    "down"
                                  ]
                                },
                                "reason": {
                                  "type": "string"
                                }
                              }
                            }
                          }
                        }
                      }
                    },
                    "override": {
                      "type": "object",
                      "required": [
                        "active"
                      ],
                      "properties": {
                        "active": {
                          "type": "boolean",
                          "description": "True while staff have set the status by hand."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "updatedAt": "2026-10-15T09:40:12.000Z",
                  "internet": {
                    "level": "ok",
                    "attribution": "ok"
                  },
                  "spots": [
                    {
                      "key": "coworking",
                      "level": "ok",
                      "showAps": false,
                      "aps": []
                    },
                    {
                      "key": "terrain",
                      "level": "limited",
                      "showAps": true,
                      "aps": [
                        {
                          "key": "cabin-3",
                          "level": "limited",
                          "reason": "flapping"
                        }
                      ]
                    },
                    {
                      "key": "molino",
                      "level": "ok",
                      "showAps": true,
                      "aps": []
                    }
                  ],
                  "override": {
                    "active": false
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not on the finca's network and no valid x-status-password header.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "unauthorized"
                    }
                  }
                },
                "example": {
                  "error": "unauthorized"
                }
              }
            }
          },
          "503": {
            "description": "The network equipment could not be read and there is no earlier snapshot to return.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "unavailable"
                    }
                  }
                },
                "example": {
                  "error": "unavailable"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "statusPassword": {
        "type": "apiKey",
        "in": "header",
        "name": "x-status-password",
        "description": "The guest Wi-Fi password. Not needed from the finca network."
      }
    }
  }
}
