{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/cubi-estate/json-schema/cubi-estate-agent-filter-request-schema.json",
  "title": "AgentFilterRequest",
  "x-generated": "2026-11-04",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/cubi-estate-openapi.yml#/components/schemas/AgentFilterRequest",
  "properties": {
    "location": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "title": "Location",
      "description": "Locations (countries, cities, regions, addresses)."
    },
    "type": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "title": "Type",
      "description": "Property types (apartment, house, villa, land, etc.)."
    },
    "transaction_type": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "sale",
            "rent"
          ]
        },
        {
          "type": "null"
        }
      ],
      "title": "Transaction Type",
      "description": "'sale' (buy) or 'rent'. Omit/null defaults to sale."
    },
    "price_period": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "night",
            "week",
            "month"
          ]
        },
        {
          "type": "null"
        }
      ],
      "title": "Price Period",
      "description": "Rental billing period of the budget: 'night' (short-stay/camping), 'week', or 'month' (long-term). Only with transaction_type='rent'."
    },
    "min_price": {
      "anyOf": [
        {
          "type": "number",
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Min Price"
    },
    "max_price": {
      "anyOf": [
        {
          "type": "number",
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Max Price"
    },
    "min_bedrooms": {
      "anyOf": [
        {
          "type": "integer",
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Min Bedrooms"
    },
    "max_bedrooms": {
      "anyOf": [
        {
          "type": "integer",
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Max Bedrooms"
    },
    "min_floor": {
      "anyOf": [
        {
          "type": "integer",
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Min Floor"
    },
    "max_floor": {
      "anyOf": [
        {
          "type": "integer",
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Max Floor"
    },
    "min_living_area": {
      "anyOf": [
        {
          "type": "number",
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Min Living Area"
    },
    "max_living_area": {
      "anyOf": [
        {
          "type": "number",
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Max Living Area"
    },
    "min_plot_area": {
      "anyOf": [
        {
          "type": "number",
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Min Plot Area"
    },
    "max_plot_area": {
      "anyOf": [
        {
          "type": "number",
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Max Plot Area"
    },
    "min_bathrooms": {
      "anyOf": [
        {
          "type": "integer",
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Min Bathrooms"
    },
    "max_bathrooms": {
      "anyOf": [
        {
          "type": "integer",
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Max Bathrooms"
    },
    "has_parking": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ],
      "title": "Has Parking",
      "description": "Requires at least one parking space."
    },
    "min_year_built": {
      "anyOf": [
        {
          "type": "integer",
          "maximum": 2100.0,
          "minimum": 1800.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Min Year Built"
    },
    "max_year_built": {
      "anyOf": [
        {
          "type": "integer",
          "maximum": 2100.0,
          "minimum": 1800.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Max Year Built"
    },
    "features": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "title": "Features",
      "description": "Structural features (Balcony, Pool, ...)."
    },
    "amenities": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "title": "Amenities",
      "description": "Service amenities (Gym, Security, ...)."
    },
    "radius_center": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Radius Center",
      "description": "Center place of a radius search ('within X km of PLACE'). Replaces the location filter when resolvable; needs radius_km."
    },
    "radius_km": {
      "anyOf": [
        {
          "type": "number",
          "maximum": 300.0,
          "exclusiveMinimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Radius Km",
      "description": "Radius in km around radius_center (under 1 km is searched as 1 km)."
    },
    "dimension_filters": {
      "items": {
        "$ref": "#/$defs/DimensionConstraint"
      },
      "type": "array",
      "title": "Dimension Filters",
      "description": "Measurable constraints: [{dimension_key, op, value, value_max?, weight?}]. Keys: quantity.bedrooms, quantity.bathrooms, quantity.parking_spaces, quantity.rooms_total, quantity.balconies, area.living, area.plot, area.balcony_total, area.terrace_total, area.garden, level.floor_number, level.total_floors, dimension.ceiling_height. op ∈ eq|gt|gte|lt|lte|between (value_max for between); values in m² / m / count / floor. weight 'must' filters, 'nice' only ranks."
    },
    "updated_since": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Updated Since",
      "description": "Only listings whose data changed at or after this instant. ISO-8601, e.g. 2026-09-21T06:00:00Z; naive values are read as UTC. `listings.updated_at` moves when a listing's DATA changes, not when a sync re-confirms it unchanged, so this answers 'what actually moved' rather than 'what was looked at'. The reply then carries `changed`, `checked_at` (pass it back as updated_since next time), `next_poll_at` and `next_poll_after_seconds`: listing data moves at the ingestion sync, so a repeat before that instant cannot return anything new."
    },
    "sort": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "newest",
            "relevance"
          ]
        },
        {
          "type": "null"
        }
      ],
      "title": "Sort",
      "description": "'newest' = most recently listed first (by first-seen time). Omit/'relevance' keeps the default stable order, which is NOT recency: reading only the top `limit` of it shows the same rows every time."
    },
    "city_only": {
      "type": "boolean",
      "title": "City Only",
      "description": "Count a place name that is also its district, province or region (Porto, Valencia) as the city only. Default false: every reading.",
      "default": false
    },
    "lang": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Lang",
      "description": "ISO 639-1; one of en/pt/es/fr/de/nl/ru.",
      "default": "en"
    },
    "limit": {
      "anyOf": [
        {
          "type": "integer",
          "maximum": 50.0,
          "minimum": 1.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Limit",
      "description": "Max listings to return.",
      "default": 10
    }
  },
  "type": "object",
  "$defs": {
    "DimensionConstraint": {
      "properties": {
        "dimension_key": {
          "type": "string",
          "title": "Dimension Key",
          "description": "dimension_catalog.dimension_key"
        },
        "op": {
          "type": "string",
          "enum": [
            "eq",
            "gt",
            "gte",
            "lt",
            "lte",
            "between"
          ],
          "title": "Op",
          "default": "gte"
        },
        "value": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Value",
          "description": "Value, or low end for between"
        },
        "value_max": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Value Max",
          "description": "High end for op=between"
        },
        "weight": {
          "type": "string",
          "enum": [
            "must",
            "nice"
          ],
          "title": "Weight",
          "default": "must"
        }
      },
      "type": "object",
      "required": [
        "dimension_key"
      ],
      "title": "DimensionConstraint",
      "description": "One deterministic measurable constraint (mig 20261019101745).\n\n`dimension_key` is a `dimension_catalog` key — a PREDICATE such as\n``quantity.parking_spaces`` or ``area.balcony_total``, never an instance\n(``parking1``). `op` + `value` (+ `value_max` for ``between``) in the\ndimension's canonical unit (sqm / m / count / floor). ``must`` compiles to\na typed EXISTS over `listing_dimension_value` (or the hot column);\n``nice`` is a soft preference — \"a big balcony\" ranks, it never excludes."
    }
  }
}