{
  "openapi": "3.1.0",
  "info": {
    "title": "Homivo search",
    "version": "1.0.0",
    "description": "Search homes by what they are actually like. Read GET /api/search/guide first. It lists the places that have homes and the phrases the search understands. Use those words in one sentence. No API key. About 30 requests a minute per IP. A 429 response includes Retry-After. Each hit includes reasons and survey, which are the photograph and advert evidence."
  },
  "servers": [{ "url": "https://homivo.co.uk" }],
  "paths": {
    "/api/search": {
      "get": {
        "operationId": "searchHomes",
        "summary": "Search homes Homivo has indexed",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": { "type": "string" },
            "description": "One sentence. Use a place and phrases from GET /api/search/guide. Example: cottage with a sea view near Oban.",
            "example": "cottage with a sea view near Oban"
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": { "type": "integer", "minimum": 0, "maximum": 500, "default": 0 }
          }
        ],
        "responses": {
          "200": {
            "description": "Matches, with why each home matched and what the photographs show.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SearchResponse" }
              }
            }
          },
          "429": {
            "description": "Too many searches from this IP.",
            "headers": {
              "Retry-After": {
                "schema": { "type": "integer" },
                "description": "Seconds to wait before trying again."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "error": { "type": "string" } }
                }
              }
            }
          }
        }
      }
    },
    "/api/search/guide": {
      "get": {
        "operationId": "searchGuide",
        "summary": "Places and phrases the search understands",
        "responses": {
          "200": {
            "description": "Read this before searching. places are names that currently have homes. features are the only phrases that change a result.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Guide" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "SearchResponse": {
        "type": "object",
        "required": ["results", "count"],
        "properties": {
          "count": { "type": "integer" },
          "guide": { "type": "string", "format": "uri", "description": "Where to read the place list and the phrase list." },
          "interpretation": { "$ref": "#/components/schemas/Interpretation" },
          "results": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Hit" }
          }
        }
      },
      "Interpretation": {
        "type": "object",
        "properties": {
          "chips": {
            "type": "array",
            "description": "What the sentence was understood as. kind name means those words were searched as a house name, not as a feature.",
            "items": {
              "type": "object",
              "properties": {
                "label": { "type": "string" },
                "kind": { "type": "string" }
              }
            }
          },
          "ignored": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Words from the sentence that were not used. Remove them, or replace them with a phrase from the guide, and search again."
          },
          "place": { "type": ["string", "null"] }
        }
      },
      "Guide": {
        "type": "object",
        "required": ["places", "features", "example"],
        "properties": {
          "write": { "type": "string" },
          "places": { "type": "array", "items": { "type": "string" } },
          "where": { "type": "string" },
          "features": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "group": { "type": "string" },
                "say": { "type": "string" },
                "phrases": { "type": "array", "items": { "type": "string" } }
              }
            }
          },
          "types": { "type": "array", "items": { "type": "string" } },
          "price": { "type": "string" },
          "bedrooms": { "type": "string" },
          "drive": { "type": "string" },
          "after_you_search": { "type": "string" },
          "example": { "type": "string" }
        }
      },
      "Hit": {
        "type": "object",
        "required": ["public_id", "url", "status", "reasons", "survey"],
        "properties": {
          "public_id": { "type": "string", "description": "Homivo listing number." },
          "url": { "type": "string", "format": "uri", "description": "Canonical Homivo listing page." },
          "place": { "type": ["string", "null"], "description": "Address of the home." },
          "price": { "type": ["number", "null"], "description": "Asking price in GBP." },
          "status": { "type": ["string", "null"], "description": "for_sale, under_offer, sold, or withdrawn." },
          "last_seen": { "type": ["string", "null"], "format": "date-time", "description": "When the advert was last fetched." },
          "source_url": { "type": ["string", "null"], "format": "uri", "description": "The estate agent's own advert." },
          "reasons": {
            "type": "array",
            "description": "Why this home matched, with the evidence.",
            "items": { "$ref": "#/components/schemas/Reason" }
          },
          "survey": {
            "type": "array",
            "description": "What the photographs show, grouped by room. The same sentences as the listing page, trimmed.",
            "items": { "$ref": "#/components/schemas/RoomSurvey" }
          }
        }
      },
      "Reason": {
        "type": "object",
        "properties": {
          "key": { "type": "string" },
          "label": { "type": "string" },
          "detail": { "type": "string" },
          "confidence": { "type": ["number", "null"] },
          "sources": { "type": "array", "items": { "type": "string" }, "description": "text, vision, or geo." }
        }
      },
      "RoomSurvey": {
        "type": "object",
        "required": ["room", "notes"],
        "properties": {
          "room": { "type": "string" },
          "notes": { "type": "string" }
        }
      }
    }
  }
}
