{"openapi":"3.1.0","info":{"title":"Nemovizor API","version":"1.0.0","description":"Public API of Nemovizor — Czech & Slovak real-estate platform. Designed to be consumed by AI agents and machine clients.\n\n**Two parallel surfaces exist with identical semantics:**\n\n- `/api/*` — legacy paths, snake_case in both query params and response bodies. Kept stable for existing clients (including the Nemovizor web frontend).\n- `/api/v1/*` — camelCase in both query params and response bodies. Recommended for new agent integrations. It is a thin proxy over `/api/*` that rewrites keys in both directions, so rate limits, auth and behavior are identical.\n\n**Authentication:** Most read endpoints are public (per-IP rate limit). Webhooks and the import API require a Bearer API key with the appropriate scope. Saved-searches require a logged-in user session (cookie auth, not currently expressible in this spec).\n\nSee also: [llms.txt](/llms.txt), [robots.txt](/robots.txt), [ai-plugin.json](/.well-known/ai-plugin.json).","contact":{"name":"Nemovizor","email":"info@nemovizor.cz"}},"servers":[{"url":"https://www.nemovizor.cz","description":"Production"},{"url":"http://localhost:3000","description":"Local development"}],"tags":[{"name":"Listings","description":"Public property search, filters, detail and pagination."},{"name":"Brokers","description":"Broker contact (PII access, rate-limited)."},{"name":"AI","description":"AI-powered endpoints."},{"name":"Saved searches","description":"Persisted searches with alerts and delta results."},{"name":"Projects","description":"Developer projects (residential complexes) and their units."},{"name":"Valuation","description":"Property valuation via RealVisor/Valuo."},{"name":"Leads","description":"Lead capture."},{"name":"Analytics","description":"Analytics tracking and dashboards."},{"name":"Webhooks","description":"Outbound webhook subscriptions for property events."},{"name":"Import","description":"Batch import API for CRMs and agency management systems."}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Nemovizor API key issued under `/broker/api-keys`. Pass as `Authorization: Bearer <key>`. Scopes: `write:import` (batch import — agency keys only), `write:webhooks` (webhook CRUD)."}},"schemas":{"ApiError":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["VALIDATION_ERROR","NOT_FOUND","UNAUTHORIZED","FORBIDDEN","RATE_LIMITED","SERVICE_UNAVAILABLE","INTERNAL_ERROR"],"description":"Stable error code."},"message":{"type":"string","description":"Human-readable error message."},"details":{"description":"Optional machine-readable details (e.g. Zod issues)."}},"required":["code","message"]}},"required":["error"],"description":"Standard error response envelope used by all v1 endpoints."},"ListingType":{"type":"string","enum":["sale","rent","auction","shares","project"],"description":"Type of listing. `sale` = for sale, `rent` = for rent, `auction` = dražba, `shares` = podíly (share sale), `project` = new-build project."},"Category":{"type":"string","enum":["apartment","house","land","commercial","other"],"description":"Property category."},"PropertySort":{"type":"string","enum":["featured","price_asc","price_desc","newest","oldest","area_desc","area_asc"],"description":"Sort order. `featured` (default): featured listings first, then newest."},"BrokerSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":["string","null"]},"photo":{"type":["string","null"]},"agency_name":{"type":["string","null"]},"rating":{"type":["number","null"]},"bio":{"type":["string","null"]},"active_listings":{"type":["integer","null"],"minimum":0},"specialization":{"type":["string","null"]}},"required":["id","name","slug","photo","agency_name"],"additionalProperties":{}},"PropertyDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"title":{"type":"string"},"listing_type":{"allOf":[{"$ref":"#/components/schemas/ListingType"},{"type":["string","null"]}]},"category":{"allOf":[{"$ref":"#/components/schemas/Category"},{"type":["string","null"]}]},"subtype":{"type":["string","null"]},"rooms_label":{"type":["string","null"]},"price":{"type":["number","null"]},"price_note":{"type":["string","null"]},"price_currency":{"type":["string","null"]},"price_unit":{"type":["string","null"]},"price_negotiation":{"type":["boolean","null"]},"city":{"type":["string","null"]},"district":{"type":["string","null"]},"street":{"type":["string","null"]},"zip":{"type":["string","null"]},"region":{"type":["string","null"]},"city_part":{"type":["string","null"]},"country":{"type":["string","null"]},"location_label":{"type":["string","null"]},"latitude":{"type":["number","null"]},"longitude":{"type":["number","null"]},"area":{"type":["number","null"]},"land_area":{"type":["number","null"]},"built_up_area":{"type":["number","null"]},"floor_area":{"type":["number","null"]},"summary":{"type":["string","null"]},"description":{"type":["string","null"]},"condition":{"type":["string","null"]},"ownership":{"type":["string","null"]},"furnishing":{"type":["string","null"]},"energy_rating":{"type":["string","null"]},"featured":{"type":["boolean","null"]},"featured_until":{"type":["string","null"]},"active":{"type":["boolean","null"]},"created_at":{"type":["string","null"]},"updated_at":{"type":["string","null"]},"image_src":{"type":["string","null"]},"images":{"type":["array","null"],"items":{"type":"string"}},"broker_id":{"type":["string","null"],"format":"uuid"},"brokers":{"allOf":[{"$ref":"#/components/schemas/BrokerSummary"},{"type":["object","null"]}]}},"required":["id","slug","title"],"additionalProperties":{},"description":"A single property listing. The endpoint returns the raw Supabase row plus nested `brokers` summary; additional columns may appear here and will be passed through unchanged."},"PropertiesQuery":{"type":"object","properties":{"page":{"type":"string","pattern":"^\\d+$/u","description":"Page number, 1-based.","example":"1"},"limit":{"type":"string","pattern":"^\\d+$/u","description":"Page size (max 100).","example":"24"},"listing_type":{"$ref":"#/components/schemas/ListingType"},"category":{"type":"string","minLength":1,"description":"Single category or comma-separated list, e.g. `apartment,house`."},"subtype":{"type":"string","minLength":1,"description":"Single subtype or comma-separated list, e.g. `2+kk,3+kk`."},"city":{"type":"string","minLength":1},"country":{"type":"string","minLength":1,"description":"Single ISO country code or comma-separated list, e.g. `cz,sk`."},"broker_id":{"type":"string","format":"uuid"},"agency_id":{"type":"string","format":"uuid"},"price_min":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"price_max":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"area_min":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"area_max":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"sw_lat":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"sw_lon":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"ne_lat":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"ne_lon":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"sort":{"$ref":"#/components/schemas/PropertySort"},"cursor":{"type":"string","minLength":1,"description":"Opaque pagination cursor returned in the previous response's `next_cursor`/`nextCursor` field. When present, the response is sorted by `(created_at desc, id desc)` and `page`/`sort` are ignored."}},"additionalProperties":{}},"PropertiesResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PropertyDto"}},"total":{"type":"integer","minimum":0,"description":"Total rows matching the filters."},"page":{"type":"integer","exclusiveMinimum":0},"pages":{"type":"integer","minimum":0},"limit":{"type":"integer","exclusiveMinimum":0},"next_cursor":{"type":["string","null"],"description":"Opaque cursor for the next page when iterating with cursor pagination. `null` on the last page; absent when offset pagination was used."}},"required":["data","total","page","pages","limit"]},"PropertyDetailResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PropertyDto"}},"required":["data"]},"AiSearchBody":{"type":"object","properties":{"query":{"type":"string","description":"Natural-language real-estate query in any supported language (Czech, Slovak, English, French, German, Italian, Spanish, Portuguese, Polish, Hungarian, Croatian, Slovenian, Dutch …). Trimmed; 3..500 chars after trimming.","example":"Byt 2+kk nebo 3+kk v Praze do 8 milionů"},"previousFilters":{"$ref":"#/components/schemas/AiSearchFilters"},"conversationId":{"type":"string","format":"uuid"}},"required":["query"]},"AiSearchFilters":{"type":"object","properties":{"listingType":{"$ref":"#/components/schemas/ListingType"},"category":{"$ref":"#/components/schemas/Category"},"subtypes":{"type":"array","items":{"type":"string"}},"city":{"type":"string"},"keywords":{"type":"array","items":{"type":"string"},"description":"Free-text or district hints (e.g. 'Vinohrady', 'Karlín', 'po rekonstrukci'). Carries qualitative signals that the deterministic parser could not assign to a hard filter."},"country":{"type":"string","description":"ISO 3166-1 alpha-2 country code, lowercase."},"priceMin":{"type":"number","exclusiveMinimum":0},"priceMax":{"type":"number","exclusiveMinimum":0},"areaMin":{"type":"number","exclusiveMinimum":0},"areaMax":{"type":"number","exclusiveMinimum":0},"energyMin":{"type":"string","enum":["A","B","C","D","E","F","G"]},"tags":{"type":"array","items":{"type":"string","enum":["balcony","terrace","garden","elevator","garage","parking","cellar","ac","barrier_free"]}},"availableFrom":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$/u"}},"description":"Filters from a previous turn — used to support multi-turn refinement."},"AiSearchDisambiguate":{"type":"object","properties":{"field":{"type":"string","description":"Field that was ambiguous (e.g. 'city')."},"options":{"type":"array","items":{"type":"string"},"description":"Up to 5 candidate values."},"reason":{"type":"string"}},"required":["field","options"]},"AiSearchResponse":{"type":"object","properties":{"filters":{"allOf":[{"$ref":"#/components/schemas/AiSearchFilters"},{"description":"Structured filters extracted from the natural-language query. All fields optional. Pass directly into GET /api/v1/properties."}]},"explanation":{"type":"string","description":"Short one-sentence summary of what the model understood, rendered in the user's input language."},"language":{"type":"string","description":"Detected language (ISO 639-1) of the query."},"confidence":{"type":"number","minimum":0,"maximum":1,"description":"0..1 confidence score for the extraction."},"tier":{"type":"string","enum":["deterministic","llm","deterministic_fallback","hybrid"],"description":"Which pipeline tier produced the answer."},"hasSemanticSignal":{"type":"boolean"},"disambiguate":{"allOf":[{"$ref":"#/components/schemas/AiSearchDisambiguate"},{"description":"Present when the parser is unsure between several values. The UI / agent should ask the user to pick one before searching."}]},"conversationId":{"type":"string","format":"uuid"}},"required":["filters","explanation","language","confidence","tier","hasSemanticSignal"]},"ValuationPropertyType":{"type":"string","enum":["flat","house","land"],"description":"Valuation property type accepted by the RealVisor/Valuo backend."},"ValuationKind":{"type":"string","enum":["sale","rent"],"description":"Whether this is a sale or a rent valuation."},"ValuationEstimateBody":{"type":"object","properties":{"propertyType":{"$ref":"#/components/schemas/ValuationPropertyType"},"lat":{"type":"number","minimum":-90,"maximum":90},"lng":{"type":"number","minimum":-180,"maximum":180},"floorArea":{"type":"number","exclusiveMinimum":0},"lotArea":{"type":"number","exclusiveMinimum":0},"rating":{"type":"number","minimum":0,"maximum":10},"kind":{"$ref":"#/components/schemas/ValuationKind"},"email":{"type":"string","format":"email"},"name":{"type":"string"},"lastName":{"type":"string"},"phone":{"type":"string"},"address":{"type":"string"},"street":{"type":"string"},"city":{"type":"string"},"district":{"type":"string"},"region":{"type":"string"},"postalCode":{"type":"string"},"localType":{"type":"string"},"ownership":{"type":"string"},"houseType":{"type":"string"},"landType":{"type":"string"},"construction":{"type":"string"},"floor":{"type":"integer"},"totalFloors":{"type":"integer","exclusiveMinimum":0},"elevator":{"type":"boolean"},"energyPerformance":{"type":"string"},"equipment":{"type":"boolean"},"easyAccess":{"type":"boolean"},"loggiaArea":{"type":"number","minimum":0},"balconyArea":{"type":"number","minimum":0},"terraceArea":{"type":"number","minimum":0},"cellarArea":{"type":"number","minimum":0},"gardenArea":{"type":"number","minimum":0},"rooms":{"type":"integer","minimum":0},"bathrooms":{"type":"integer","minimum":0},"garages":{"type":"integer","minimum":0},"parkingSpaces":{"type":"integer","minimum":0},"userId":{"type":"string","format":"uuid"}},"required":["propertyType","lat","lng","email"],"additionalProperties":{},"description":"Request body for the RealVisor/Valuo-backed property valuation. `floorArea` is required for `flat` and `house`; `land` uses `lotArea` instead."},"ValuationEstimateResult":{"type":"object","properties":{"avg_price":{"type":"number"},"min_price":{"type":"number"},"max_price":{"type":"number"},"avg_price_m2":{"type":"number"},"range_price":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"}]},"currency":{"type":"string"},"calc_area":{"type":"number"},"as_of":{"type":"string","description":"ISO YYYY-MM-DD date the valuation was computed for."}},"required":["avg_price","min_price","max_price","avg_price_m2","range_price","currency","calc_area","as_of"]},"ValuationEstimateResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"valuationId":{"type":["string","null"],"format":"uuid"},"result":{"$ref":"#/components/schemas/ValuationEstimateResult"}},"required":["success","valuationId","result"]},"ValuationStatusQuery":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Valuation report UUID returned by POST /api/valuation/estimate."}},"required":["id"]},"ValuationStatusResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"pdf_url":{"type":["string","null"],"format":"uri"},"paid":{"type":"boolean"},"ready":{"type":"boolean"}},"required":["id","pdf_url","paid","ready"]},"WebhookFilter":{"type":"object","properties":{"category":{"type":"array","items":{"type":"string"}},"subtype":{"type":"array","items":{"type":"string"}},"city":{"type":"string"},"country":{"type":"array","items":{"type":"string"}},"listing_type":{"type":"string"},"price_min":{"type":"number","minimum":0},"price_max":{"type":"number","minimum":0},"area_min":{"type":"number","minimum":0},"area_max":{"type":"number","minimum":0},"broker_id":{"type":"string","format":"uuid"}},"description":"Optional filter applied per-subscription at dispatch time. All keys are AND-combined; array values are OR-combined; numeric _min/_max are inclusive."},"CreateWebhookBody":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Target HTTPS URL that will receive POST deliveries."},"event_types":{"type":"array","items":{"type":"string","enum":["property.created","property.updated","property.deleted","property.price_changed"]},"minItems":1,"description":"Event types this subscription wants. Default: all property events."},"filter":{"allOf":[{"$ref":"#/components/schemas/WebhookFilter"},{"type":["object","null"]}]}},"required":["url"]},"UpdateWebhookBody":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"event_types":{"type":"array","items":{"type":"string","enum":["property.created","property.updated","property.deleted","property.price_changed"]},"minItems":1},"filter":{"allOf":[{"$ref":"#/components/schemas/WebhookFilter"},{"type":["object","null"]}]},"active":{"type":"boolean"}}},"WebhookSubscriptionDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"secret_prefix":{"type":"string"},"event_types":{"type":"array","items":{"type":"string","enum":["property.created","property.updated","property.deleted","property.price_changed"]}},"filter":{"allOf":[{"$ref":"#/components/schemas/WebhookFilter"},{"type":["object","null"]}]},"active":{"type":"boolean"},"failure_count":{"type":"integer","minimum":0},"disabled_at":{"type":["string","null"]},"last_delivered_at":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["id","url","secret_prefix","event_types","filter","active","failure_count","disabled_at","last_delivered_at","created_at"]},"CreateWebhookResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookSubscriptionDto"},"secret":{"type":"string","description":"Plain webhook signing secret. Returned ONCE on creation; store it immediately. Used by your endpoint to verify the X-Nemovizor-Signature header."}},"required":["data","secret"]},"WebhookListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookSubscriptionDto"}}},"required":["data"]},"WebhookDetailResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookSubscriptionDto"}},"required":["data"]},"MapPointDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"lat":{"type":"number"},"lon":{"type":"number"},"price":{"type":["number","null"]},"price_currency":{"type":["string","null"]},"category":{"allOf":[{"$ref":"#/components/schemas/Category"},{"type":["string","null"]}]},"listing_type":{"allOf":[{"$ref":"#/components/schemas/ListingType"},{"type":["string","null"]}]},"title":{"type":"string"},"slug":{"type":"string"},"rooms_label":{"type":["string","null"]},"image_src":{"type":["string","null"]},"subtype":{"type":["string","null"]},"area":{"type":["number","null"]},"district":{"type":["string","null"]}},"required":["id","lat","lon","price","price_currency","category","listing_type","title","slug","rooms_label","image_src","subtype","area","district"],"description":"Slim geo-point used for map markers. Minimal payload by design."},"MapPointsQuery":{"type":"object","properties":{"zoom":{"type":"string","pattern":"^\\d+$/u","description":"Map zoom level 1..20. zoom >= 13 limits to 500 pins.","example":"7"},"listing_type":{"$ref":"#/components/schemas/ListingType"},"category":{"type":"string","minLength":1},"subtype":{"type":"string","minLength":1},"city":{"type":"string","minLength":1},"country":{"type":"string","minLength":1},"broker_id":{"type":"string","format":"uuid"},"agency_id":{"type":"string","format":"uuid"},"price_min":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"price_max":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"area_min":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"area_max":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"sw_lat":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"sw_lon":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"ne_lat":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"ne_lon":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"}},"additionalProperties":{}},"MapPointsResponse":{"type":"object","properties":{"points":{"type":"array","items":{"$ref":"#/components/schemas/MapPointDto"}},"count":{"type":"integer","minimum":0},"total":{"type":"integer","minimum":0},"truncated":{"type":"boolean","description":"True if result was capped at the pin-limit for this zoom."}},"required":["points","count","total","truncated"]},"CountedValue":{"type":"object","properties":{"value":{"type":"string"},"count":{"type":"integer","minimum":0}},"required":["value","count"]},"NumericRange":{"type":"object","properties":{"min":{"type":"number","minimum":0},"max":{"type":"number","minimum":0}},"required":["min","max"]},"FilterOptionsQuery":{"type":"object","properties":{"listing_type":{"$ref":"#/components/schemas/ListingType"},"category":{"type":"string","minLength":1},"subtype":{"type":"string","minLength":1},"country":{"type":"string","minLength":1},"city":{"type":"string"},"price_min":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"price_max":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"area_min":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"area_max":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"broker_id":{"type":"string","format":"uuid"},"agency_id":{"type":"string","format":"uuid"},"sw_lat":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"sw_lon":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"ne_lat":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"ne_lon":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"}},"additionalProperties":{}},"FilterOptionsResponse":{"type":"object","properties":{"categories":{"type":"array","items":{"$ref":"#/components/schemas/CountedValue"}},"cities":{"type":"array","items":{"$ref":"#/components/schemas/CountedValue"},"description":"Top 100 cities by count."},"subtypes":{"type":"array","items":{"$ref":"#/components/schemas/CountedValue"}},"listingTypes":{"type":"array","items":{"$ref":"#/components/schemas/CountedValue"}},"countries":{"type":"array","items":{"$ref":"#/components/schemas/CountedValue"}},"currencies":{"type":"array","items":{"$ref":"#/components/schemas/CountedValue"}},"priceRange":{"$ref":"#/components/schemas/NumericRange"},"areaRange":{"$ref":"#/components/schemas/NumericRange"}},"required":["categories","cities","subtypes","listingTypes","priceRange","areaRange"]},"CreateSavedSearchBody":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"filters":{"allOf":[{"$ref":"#/components/schemas/AiSearchFilters"},{"description":"Structured filters extracted from the natural-language query. All fields optional. Pass directly into GET /api/v1/properties."}]},"query_text":{"type":"string","maxLength":500,"description":"Original natural-language query — never stored verbatim. The server hashes with the daily salt and discards the plaintext."},"query_lang":{"type":"string","pattern":"^[a-z]{2}$/u"},"alert_freq":{"type":"string","enum":["off","instant","daily","weekly"],"default":"off"},"notify_email":{"type":"boolean","default":true},"notify_push":{"type":"boolean","default":false}},"required":["name","filters"]},"UpdateSavedSearchBody":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"filters":{"allOf":[{"$ref":"#/components/schemas/AiSearchFilters"},{"description":"Structured filters extracted from the natural-language query. All fields optional. Pass directly into GET /api/v1/properties."}]},"alert_freq":{"type":"string","enum":["off","instant","daily","weekly"]},"notify_email":{"type":"boolean"},"notify_push":{"type":"boolean"}}},"SavedSearch":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"filters":{"allOf":[{"$ref":"#/components/schemas/AiSearchFilters"},{"description":"Structured filters extracted from the natural-language query. All fields optional. Pass directly into GET /api/v1/properties."}]},"query_lang":{"type":["string","null"]},"alert_freq":{"type":"string","enum":["off","instant","daily","weekly"]},"notify_email":{"type":"boolean"},"notify_push":{"type":"boolean"},"last_checked_at":{"type":["string","null"]},"last_notified_at":{"type":["string","null"]},"match_count":{"type":"integer","minimum":0},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","name","filters","alert_freq","notify_email","notify_push","match_count","created_at","updated_at"]},"SavedSearchListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SavedSearch"}}},"required":["data"]},"SavedSearchDetailResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SavedSearch"}},"required":["data"]},"SavedSearchResultsResponse":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":{}},"description":"Properties matching the saved search since the last watermark."},"since":{"type":["string","null"]},"total":{"type":"integer","minimum":0}},"required":["data","since","total"]},"SemanticSearchBody":{"type":"object","properties":{"query":{"type":"string","minLength":3,"maxLength":500},"filters":{"type":"object","properties":{"listingType":{"type":"string"},"country":{"type":"string"},"city":{"type":"string"},"category":{"type":"string"},"priceMin":{"type":"number","exclusiveMinimum":0},"priceMax":{"type":"number","exclusiveMinimum":0}}},"limit":{"type":"integer","minimum":1,"maximum":100}},"required":["query"]},"SemanticSearchResponse":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":{}}},"total":{"type":"integer","minimum":0},"anchors":{"type":"object","additionalProperties":{}},"embedding_model":{"type":"string"},"query_lang":{"type":"string"}},"required":["data","total","anchors","embedding_model","query_lang"]},"ProjectsQuery":{"type":"object","properties":{"page":{"type":"string","pattern":"^\\d+$/u"},"limit":{"type":"string","pattern":"^\\d+$/u"},"city":{"type":"string","minLength":1},"agency_id":{"type":"string","format":"uuid"},"developer_ico":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"status":{"type":"string","enum":["planned","active","construction","selling","completed","archived"]},"cursor":{"type":"string","minLength":1}},"additionalProperties":{}},"ProjectImageDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string"},"url_thumbnail":{"type":["string","null"]},"alt_text":{"type":["string","null"]},"sort_order":{"type":"integer","minimum":0},"is_main":{"type":"boolean"},"photo_kind":{"type":"string","enum":["photo","floor_plan"]}},"required":["id","url","sort_order","is_main","photo_kind"]},"ProjectDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"name":{"type":"string"},"summary":{"type":["string","null"]},"description":{"type":["string","null"]},"city":{"type":["string","null"]},"district":{"type":["string","null"]},"street":{"type":["string","null"]},"zip":{"type":["string","null"]},"region":{"type":["string","null"]},"location_label":{"type":["string","null"]},"latitude":{"type":["number","null"]},"longitude":{"type":["number","null"]},"developer_name":{"type":["string","null"]},"developer_ico":{"type":["number","null"]},"project_lifetime":{"type":["integer","null"]},"total_units":{"type":"integer","minimum":0},"available_units":{"type":"integer","minimum":0},"sold_units":{"type":"integer","minimum":0},"price_from":{"type":["number","null"]},"price_to":{"type":["number","null"]},"area_from":{"type":["number","null"]},"area_to":{"type":["number","null"]},"construction_start":{"type":["string","null"]},"estimated_completion":{"type":["string","null"]},"actual_completion":{"type":["string","null"]},"date_move":{"type":["string","null"]},"date_sale":{"type":["string","null"]},"project_url":{"type":["string","null"]},"matterport_url":{"type":["string","null"]},"mapy_panorama_url":{"type":["string","null"]},"image_src":{"type":["string","null"]},"images":{"type":["array","null"],"items":{"type":"string"}},"project_images":{"type":"array","items":{"$ref":"#/components/schemas/ProjectImageDto"}},"status":{"type":["string","null"]},"active":{"type":["boolean","null"]},"featured":{"type":["boolean","null"]},"created_at":{"type":["string","null"]},"updated_at":{"type":["string","null"]}},"required":["id","slug","name","total_units","available_units","sold_units"],"additionalProperties":{}},"ProjectsResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ProjectDto"}},"total":{"type":"integer","minimum":0},"page":{"type":"integer","exclusiveMinimum":0},"pages":{"type":"integer","minimum":0},"limit":{"type":"integer","exclusiveMinimum":0},"next_cursor":{"type":["string","null"]}},"required":["data","total","page","pages","limit"]},"ProjectDetailResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ProjectDto"}},"required":["data"]},"LeadsBody":{"type":"object","properties":{"name":{"type":"string","minLength":1,"description":"Contact full name."},"email":{"type":"string","format":"email"},"phone":{"type":"string","minLength":1},"propertyType":{"type":"string","description":"Free-form property type (e.g. 'byt', 'dum', 'pozemek')."},"intent":{"type":"string","description":"Why the lead is reaching out — 'prodat', 'koupit', 'ocenit', 'pronajmout', 'odhad'."},"address":{"type":"string"},"note":{"type":"string","maxLength":2000},"source":{"type":"string","description":"Origin of the lead (e.g. 'prodat-page', 'nemovizor-oceneni')."}},"required":["name","email"],"additionalProperties":{}},"LeadsResponse":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]},"AnalyticsEvent":{"type":"object","properties":{"session_id":{"type":"string","minLength":1,"maxLength":64,"description":"Stable client session id."},"user_id":{"type":["string","null"],"format":"uuid"},"event_type":{"type":"string","minLength":1,"maxLength":64,"description":"Event type identifier, e.g. `property_view`, `phone_click`, `favorite_toggle`."},"properties":{"type":"object","additionalProperties":{}},"url":{"type":"string","maxLength":500},"referrer":{"type":"string","maxLength":500},"device_type":{"type":"string","maxLength":16}},"required":["session_id","event_type"],"additionalProperties":{}},"AnalyticsTrackBody":{"anyOf":[{"$ref":"#/components/schemas/AnalyticsEvent"},{"type":"array","items":{"$ref":"#/components/schemas/AnalyticsEvent"}}],"description":"A single analytics event or an array of events. Events missing `session_id` or `event_type` are silently dropped."},"AnalyticsTrackResponse":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]},"EventCountPair":{"type":"object","properties":{"type":{"type":"string"},"count":{"type":"integer","minimum":0}},"required":["type","count"]},"FunnelStep":{"type":"object","properties":{"step":{"type":"string"},"sessions":{"type":"integer","minimum":0}},"required":["step","sessions"]},"DailyViewPoint":{"type":"object","properties":{"date":{"type":"string","description":"ISO YYYY-MM-DD."},"views":{"type":"integer","minimum":0}},"required":["date","views"]},"TopPropertyAnalyticsRow":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"count":{"type":"integer","minimum":0},"title":{"type":"string"},"price":{"type":"number"},"price_currency":{"type":"string"},"image_src":{"type":"string"},"listing_type":{"type":"string"},"category":{"type":"string"},"area":{"type":"number"},"rooms_label":{"type":"string"},"slug":{"type":"string"},"city":{"type":"string"}},"required":["id","count","title","price","price_currency","image_src","listing_type","category","area","rooms_label","slug","city"],"additionalProperties":{}},"BrokerAnalyticsQuery":{"type":"object","properties":{"broker_id":{"type":"string","format":"uuid"},"agency_id":{"type":"string","format":"uuid"}},"additionalProperties":{}},"BrokerAnalyticsResponse":{"type":"object","properties":{"totalProperties":{"type":"integer","minimum":0},"total7d":{"type":"integer","minimum":0},"uniqueSessions":{"type":"integer","minimum":0},"propViews7d":{"type":"integer","minimum":0},"impressions7d":{"type":"integer","minimum":0},"contactClicks":{"type":"integer","minimum":0},"favActions":{"type":"integer","minimum":0},"mapClicks":{"type":"integer","minimum":0},"avgScrollDepth":{"type":"integer","minimum":0},"avgTimeOnPage":{"type":"integer","minimum":0},"eventBreakdown":{"type":"array","items":{"$ref":"#/components/schemas/EventCountPair"}},"deviceCounts":{"type":"object","additionalProperties":{"type":"integer","minimum":0}},"topProperties":{"type":"array","items":{"$ref":"#/components/schemas/TopPropertyAnalyticsRow"}},"hourCounts":{"type":"array","items":{"type":"integer","minimum":0}},"funnel":{"type":"array","items":{"$ref":"#/components/schemas/FunnelStep"}},"dailyViews":{"type":"array","items":{"$ref":"#/components/schemas/DailyViewPoint"}},"contactRequests":{"type":"integer","minimum":0},"newContactRequests":{"type":"integer","minimum":0}},"required":["totalProperties","total7d","uniqueSessions","propViews7d","impressions7d","contactClicks","favActions","mapClicks","avgScrollDepth","avgTimeOnPage","eventBreakdown","deviceCounts","topProperties","hourCounts","funnel","dailyViews","contactRequests","newContactRequests"]},"BrokerContactResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":["string","null"]},"phone":{"type":["string","null"]},"email":{"type":["string","null"]}},"required":["id","name","slug","phone","email"]}},"required":["data"]}},"parameters":{}},"paths":{"/api/properties":{"get":{"summary":"Search real-estate listings","description":"Paginated, server-side filtered property list. Public, cacheable. Returns raw Supabase rows plus nested broker summary.","tags":["Listings"],"parameters":[{"schema":{"type":"string","pattern":"^\\d+$/u","description":"Page number, 1-based.","example":"1"},"required":false,"description":"Page number, 1-based.","name":"page","in":"query"},{"schema":{"type":"string","pattern":"^\\d+$/u","description":"Page size (max 100).","example":"24"},"required":false,"description":"Page size (max 100).","name":"limit","in":"query"},{"schema":{"$ref":"#/components/schemas/ListingType"},"required":false,"description":"Type of listing. `sale` = for sale, `rent` = for rent, `auction` = dražba, `shares` = podíly (share sale), `project` = new-build project.","name":"listing_type","in":"query"},{"schema":{"type":"string","minLength":1,"description":"Single category or comma-separated list, e.g. `apartment,house`."},"required":false,"description":"Single category or comma-separated list, e.g. `apartment,house`.","name":"category","in":"query"},{"schema":{"type":"string","minLength":1,"description":"Single subtype or comma-separated list, e.g. `2+kk,3+kk`."},"required":false,"description":"Single subtype or comma-separated list, e.g. `2+kk,3+kk`.","name":"subtype","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"city","in":"query"},{"schema":{"type":"string","minLength":1,"description":"Single ISO country code or comma-separated list, e.g. `cz,sk`."},"required":false,"description":"Single ISO country code or comma-separated list, e.g. `cz,sk`.","name":"country","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"broker_id","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"agency_id","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"price_min","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"price_max","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"area_min","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"area_max","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"sw_lat","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"sw_lon","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"ne_lat","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"ne_lon","in":"query"},{"schema":{"$ref":"#/components/schemas/PropertySort"},"required":false,"description":"Sort order. `featured` (default): featured listings first, then newest.","name":"sort","in":"query"},{"schema":{"type":"string","minLength":1,"description":"Opaque pagination cursor returned in the previous response's `next_cursor`/`nextCursor` field. When present, the response is sorted by `(created_at desc, id desc)` and `page`/`sort` are ignored."},"required":false,"description":"Opaque pagination cursor returned in the previous response's `next_cursor`/`nextCursor` field. When present, the response is sorted by `(created_at desc, id desc)` and `page`/`sort` are ignored.","name":"cursor","in":"query"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertiesResponse"}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Supabase not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/properties":{"get":{"summary":"Search listings (camelCase, v1)","description":"camelCase proxy over `/api/properties`. Query params and response keys are camelCase. Business-sensitive fields and broker PII are stripped from the response.","tags":["Listings"],"parameters":[{"schema":{"type":"string","pattern":"^\\d+$/u","description":"Page number, 1-based.","example":"1"},"required":false,"description":"Page number, 1-based.","name":"page","in":"query"},{"schema":{"type":"string","pattern":"^\\d+$/u","description":"Page size (max 100).","example":"24"},"required":false,"description":"Page size (max 100).","name":"limit","in":"query"},{"schema":{"$ref":"#/components/schemas/ListingType"},"required":false,"description":"Type of listing. `sale` = for sale, `rent` = for rent, `auction` = dražba, `shares` = podíly (share sale), `project` = new-build project.","name":"listing_type","in":"query"},{"schema":{"type":"string","minLength":1,"description":"Single category or comma-separated list, e.g. `apartment,house`."},"required":false,"description":"Single category or comma-separated list, e.g. `apartment,house`.","name":"category","in":"query"},{"schema":{"type":"string","minLength":1,"description":"Single subtype or comma-separated list, e.g. `2+kk,3+kk`."},"required":false,"description":"Single subtype or comma-separated list, e.g. `2+kk,3+kk`.","name":"subtype","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"city","in":"query"},{"schema":{"type":"string","minLength":1,"description":"Single ISO country code or comma-separated list, e.g. `cz,sk`."},"required":false,"description":"Single ISO country code or comma-separated list, e.g. `cz,sk`.","name":"country","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"broker_id","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"agency_id","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"price_min","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"price_max","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"area_min","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"area_max","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"sw_lat","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"sw_lon","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"ne_lat","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"ne_lon","in":"query"},{"schema":{"$ref":"#/components/schemas/PropertySort"},"required":false,"description":"Sort order. `featured` (default): featured listings first, then newest.","name":"sort","in":"query"},{"schema":{"type":"string","minLength":1,"description":"Opaque pagination cursor returned in the previous response's `next_cursor`/`nextCursor` field. When present, the response is sorted by `(created_at desc, id desc)` and `page`/`sort` are ignored."},"required":false,"description":"Opaque pagination cursor returned in the previous response's `next_cursor`/`nextCursor` field. When present, the response is sorted by `(created_at desc, id desc)` and `page`/`sort` are ignored.","name":"cursor","in":"query"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertiesResponse"}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/filter-options":{"get":{"summary":"Faceted filter metadata (camelCase, v1)","description":"camelCase proxy over `/api/filter-options`. Returns available categories, subtypes, cities, price and area ranges with counts, optionally scoped by current filter selection.","tags":["Listings"],"parameters":[{"schema":{"$ref":"#/components/schemas/ListingType"},"required":false,"description":"Type of listing. `sale` = for sale, `rent` = for rent, `auction` = dražba, `shares` = podíly (share sale), `project` = new-build project.","name":"listing_type","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"category","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"subtype","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"country","in":"query"},{"schema":{"type":"string"},"required":false,"name":"city","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"price_min","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"price_max","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"area_min","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"area_max","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"broker_id","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"agency_id","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"sw_lat","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"sw_lon","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"ne_lat","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"ne_lon","in":"query"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FilterOptionsResponse"}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/map-points":{"get":{"summary":"Map geo-points (camelCase, v1)","description":"camelCase proxy over `/api/map-points`. Lightweight lat/lon + metadata for map markers. Zoom >= 13 returns up to 500 pins, otherwise up to 2000.","tags":["Listings"],"parameters":[{"schema":{"type":"string","pattern":"^\\d+$/u","description":"Map zoom level 1..20. zoom >= 13 limits to 500 pins.","example":"7"},"required":false,"description":"Map zoom level 1..20. zoom >= 13 limits to 500 pins.","name":"zoom","in":"query"},{"schema":{"$ref":"#/components/schemas/ListingType"},"required":false,"description":"Type of listing. `sale` = for sale, `rent` = for rent, `auction` = dražba, `shares` = podíly (share sale), `project` = new-build project.","name":"listing_type","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"category","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"subtype","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"city","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"country","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"broker_id","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"agency_id","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"price_min","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"price_max","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"area_min","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"area_max","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"sw_lat","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"sw_lon","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"ne_lat","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"ne_lon","in":"query"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MapPointsResponse"}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/properties/{id}":{"get":{"summary":"Fetch a single property by UUID (camelCase, v1)","description":"Returns one active property by its UUID. Response is camelCase, business-sensitive fields (commission, mortgagePercent, …) and broker PII (phone, email) are excluded. Use `GET /api/v1/brokers/{id}/contact` for explicit broker contact retrieval.","tags":["Listings"],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Property UUID returned in list responses."},"required":true,"description":"Property UUID returned in list responses.","name":"id","in":"path"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertyDetailResponse"}}}},"400":{"description":"Invalid UUID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found or inactive","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/properties/by-slug/{slug}":{"get":{"summary":"Fetch a single property by slug (camelCase, v1)","description":"Same shape as `/api/v1/properties/{id}` but resolves by URL slug instead of UUID. Useful for SEO-friendly external URLs.","tags":["Listings"],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":300,"description":"Property slug."},"required":true,"description":"Property slug.","name":"slug","in":"path"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertyDetailResponse"}}}},"400":{"description":"Invalid slug","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found or inactive","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/brokers/{id}/contact":{"get":{"summary":"Get a single broker's contact info (PII)","description":"Explicit, rate-limited PII access. Returns the broker's `phone` and `email`. Anonymous callers are limited to 10 requests/min/IP (anti-harvesting). API key callers get the per-key ceiling. Listing responses do NOT include this data — call this endpoint only when displaying a single broker after a meaningful user action.","tags":["Brokers"],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Broker UUID."},"required":true,"description":"Broker UUID.","name":"id","in":"path"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrokerContactResponse"}}}},"400":{"description":"Invalid UUID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Broker not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/map-points":{"get":{"summary":"Lightweight geo points for map markers","description":"Slim geo-point payload designed for Leaflet markerClusterGroup. Zoom levels < 13 return up to 2000 pins, >= 13 up to 500.","tags":["Listings"],"parameters":[{"schema":{"type":"string","pattern":"^\\d+$/u","description":"Map zoom level 1..20. zoom >= 13 limits to 500 pins.","example":"7"},"required":false,"description":"Map zoom level 1..20. zoom >= 13 limits to 500 pins.","name":"zoom","in":"query"},{"schema":{"$ref":"#/components/schemas/ListingType"},"required":false,"description":"Type of listing. `sale` = for sale, `rent` = for rent, `auction` = dražba, `shares` = podíly (share sale), `project` = new-build project.","name":"listing_type","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"category","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"subtype","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"city","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"country","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"broker_id","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"agency_id","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"price_min","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"price_max","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"area_min","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"area_max","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"sw_lat","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"sw_lon","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"ne_lat","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"ne_lon","in":"query"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MapPointsResponse"}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Supabase not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/filter-options":{"get":{"summary":"Faceted search metadata (available filter values + counts)","description":"Aggregate counts for filter dropdowns, optionally scoped by current filter selection and bounding box.","tags":["Listings"],"parameters":[{"schema":{"$ref":"#/components/schemas/ListingType"},"required":false,"description":"Type of listing. `sale` = for sale, `rent` = for rent, `auction` = dražba, `shares` = podíly (share sale), `project` = new-build project.","name":"listing_type","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"category","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"subtype","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"country","in":"query"},{"schema":{"type":"string"},"required":false,"name":"city","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"price_min","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"price_max","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"area_min","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"area_max","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"broker_id","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"agency_id","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"sw_lat","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"sw_lon","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"ne_lat","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"ne_lon","in":"query"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FilterOptionsResponse"}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Supabase not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/ai-search":{"post":{"summary":"Natural-language → structured property filters (multi-tier)","description":"Two-tier dispatcher converts a free-form query into structured filters. Tier 1 is a deterministic regex+alias parser that handles ~80% of traffic in <5ms with no LLM call. Tier 2 is Google Gemini 2.0 Flash with `responseSchema` enforcement, invoked only for ambiguous or semantic queries. Multi-turn refinement via `previousFilters`. 14 languages. Returns `tier`, `confidence`, `language`, `disambiguate?`, `hasSemanticSignal` so callers can decide whether to follow up with `/api/v1/semantic-search`.","tags":["AI"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiSearchBody"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiSearchResponse"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited (per-IP or per-API-key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"AI provider failure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"AI provider not configured / temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/semantic-search":{"post":{"summary":"Hybrid retrieval: deterministic anchors ∩ vector similarity","description":"Returns up to N properties ordered by a hybrid score combining 60% deterministic anchor match (city, listing type, price range), 30% cosine similarity over Gemini text-embedding-004 vectors of property text, and 10% freshness. Use this whenever the user query carries qualitative signals ('klidná lokalita', 'blízko parku', 'moderní kuchyně'). Requires the property embedding backfill to have completed for the relevant rows; falls back to 503 when the embedding service is unavailable.","tags":["AI"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SemanticSearchBody"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SemanticSearchResponse"}}}},"400":{"description":"Invalid body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Embedding service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/saved-searches":{"get":{"summary":"List the authenticated user's saved searches","tags":["Saved searches"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedSearchListResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"post":{"summary":"Create a saved search (with optional alert schedule)","description":"Persists a set of AI-search filters server-side. When `alert_freq` is `instant`, `daily`, or `weekly`, the cron job at `/api/cron/saved-search-alerts` will notify the user when new matching properties appear.","tags":["Saved searches"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSavedSearchBody"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedSearchDetailResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/saved-searches/{id}":{"get":{"summary":"Get one saved search by id","tags":["Saved searches"],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedSearchDetailResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"patch":{"summary":"Update a saved search","tags":["Saved searches"],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSavedSearchBody"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedSearchDetailResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"summary":"Delete a saved search","tags":["Saved searches"],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/saved-searches/{id}/results":{"get":{"summary":"Delta endpoint — return only matches newer than the watermark","description":"Returns property rows matching the saved search, optionally filtered by `?since=<ISO>`. The watermark advances on every call so subsequent invocations only see what's new. Drives 'connect this saved search to my agent' workflows.","tags":["Saved searches"],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","format":"date-time"},"required":false,"name":"since","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedSearchResultsResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/projects":{"get":{"summary":"List developer projects (residential complexes)","description":"Paginated list of active developer projects. Filter by city, agency_id, developer_ico, or status. Each row carries the rollup stats (`total_units`, `available_units`, `sold_units`, `price_from`, `price_to`) maintained automatically by `refresh_project_stats()` whenever a linked property changes.","tags":["Projects"],"parameters":[{"schema":{"type":"string","pattern":"^\\d+$/u"},"required":false,"name":"page","in":"query"},{"schema":{"type":"string","pattern":"^\\d+$/u"},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"city","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"agency_id","in":"query"},{"schema":{"type":"string","pattern":"^-?\\d+(?:\\.\\d+)?$/u"},"required":false,"name":"developer_ico","in":"query"},{"schema":{"type":"string","enum":["planned","active","construction","selling","completed","archived"]},"required":false,"name":"status","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"cursor","in":"query"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectsResponse"}}}}}}},"/api/v1/projects/{id}":{"get":{"summary":"Get a developer project by UUID","description":"Returns the full row plus the `project_images` gallery (with thumbnails, alt text, photo_kind 'photo' | 'floor_plan').","tags":["Projects"],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectDetailResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/projects/by-slug/{slug}":{"get":{"summary":"Get a developer project by SEO slug","tags":["Projects"],"parameters":[{"schema":{"type":"string"},"required":true,"name":"slug","in":"path"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectDetailResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/projects/{id}/units":{"get":{"summary":"List individual properties (flats / houses) belonging to a project","description":"Drives 'show me the cheapest available unit in this project' agent flows. Sorted by price ascending. Each row is the same shape as `/api/v1/properties` returns.","tags":["Projects"],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":{}}},"total":{"type":"integer","minimum":0}},"required":["data","total"]}}}}}}},"/api/valuation/estimate":{"post":{"summary":"Request a property valuation","description":"Submits property details to the RealVisor → Valuo backend and returns an estimated price range. Rate-limited to 30/min (IP-based, unless an API key raises the ceiling). Creates a `valuation_reports` row and a lead as a side effect.","tags":["Valuation"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValuationEstimateBody"}}}},"responses":{"200":{"description":"Valuation computed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValuationEstimateResponse"}}}},"400":{"description":"Invalid body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"502":{"description":"Upstream valuation provider failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Valuation provider not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/valuation/status":{"get":{"summary":"Check if a valuation PDF is ready","description":"Polls the status of a previously requested valuation. Safe to poll frequently.","tags":["Valuation"],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Valuation report UUID returned by POST /api/valuation/estimate."},"required":true,"description":"Valuation report UUID returned by POST /api/valuation/estimate.","name":"id","in":"query"}],"responses":{"200":{"description":"Current status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValuationStatusResponse"}}}},"400":{"description":"Invalid query","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Valuation not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Supabase not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/leads":{"post":{"summary":"Submit a lead (seller / buyer / other intent)","description":"Public lead capture. Rate-limited to 10/min per client to mitigate spam. All fields except `name` and `email` are optional.","tags":["Leads"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadsBody"}}}},"responses":{"200":{"description":"Lead stored","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadsResponse"}}}},"400":{"description":"Invalid body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Supabase not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/analytics/track":{"post":{"summary":"Track client-side analytics events","description":"Accepts either a single event or an array of events. Events with missing `session_id` or `event_type` are silently dropped. High rate limit (2000/min) to allow bursts.","tags":["Analytics"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsTrackBody"}}}},"responses":{"200":{"description":"Accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsTrackResponse"}}}},"400":{"description":"Invalid body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Supabase not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/webhooks":{"post":{"summary":"Create a webhook subscription","description":"Register a URL to receive HTTPS POST notifications when properties are created, updated, deleted, or change price. The plain webhook signing secret is returned ONCE in the response — store it immediately. All deliveries are signed with HMAC-SHA256 in the `X-Nemovizor-Signature: sha256=<hex>` header so receivers can verify authenticity. Requires an API key with the `write:webhooks` scope.","tags":["Webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookBody"}}}},"responses":{"201":{"description":"Subscription created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookResponse"}}}},"400":{"description":"Invalid body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"API key missing write:webhooks scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Per-owner subscription quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"summary":"List your webhook subscriptions","tags":["Webhooks"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookListResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"API key missing write:webhooks scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/webhooks/{id}":{"get":{"summary":"Fetch a single webhook subscription","tags":["Webhooks"],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDetailResponse"}}}},"400":{"description":"Invalid id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"API key missing write:webhooks scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"patch":{"summary":"Update a webhook subscription","description":"Change url, event_types, filter, or active flag.","tags":["Webhooks"],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookBody"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDetailResponse"}}}},"400":{"description":"Invalid body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"API key missing write:webhooks scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"summary":"Delete a webhook subscription","tags":["Webhooks"],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"API key missing write:webhooks scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/broker/analytics-behavior":{"get":{"summary":"Behavioral analytics dashboard for a broker or agency","description":"Returns KPIs, funnel, top properties, device breakdown and hourly activity for the specified broker or agency over the last 7 days (with 14-day daily chart). One of `broker_id` or `agency_id` is required.","tags":["Analytics"],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":false,"name":"broker_id","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"agency_id","in":"query"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrokerAnalyticsResponse"}}}},"400":{"description":"Invalid query","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Supabase not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/import/batch":{"post":{"summary":"Submit a batch import (agencies, branches, brokers, properties)","description":"Enqueues an async import job. Returns 202 with a job_id for polling via `/api/v1/import/jobs/{id}`. Requires API key with `write:import` scope and `owner_type=agency`. Items are processed in dependency order: agency → branches → brokers → properties.","tags":["Import"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"agency":{"type":"object","properties":{"external_id":{"type":"string","minLength":1,"maxLength":255},"name":{"type":"string","minLength":1,"maxLength":500},"logo":{"type":"string","format":"uri"},"description":{"type":"string","maxLength":10000},"phone":{"type":"string","maxLength":50},"email":{"type":"string","format":"email"},"website":{"type":"string","format":"uri"},"seat_city":{"type":"string","maxLength":200},"seat_address":{"type":"string","maxLength":500},"founded_year":{"type":"integer","minimum":1800,"maximum":2100},"specializations":{"type":"array","items":{"type":"string"}}},"required":["external_id","name"],"additionalProperties":{}},"branches":{"type":"array","items":{"type":"object","properties":{"external_id":{"type":"string","minLength":1,"maxLength":255},"name":{"type":"string","minLength":1,"maxLength":500},"address":{"type":"string","maxLength":500},"city":{"type":"string","maxLength":200},"zip":{"type":"string","maxLength":20},"phone":{"type":"string","maxLength":50},"email":{"type":"string","format":"email"},"latitude":{"type":"number","minimum":-90,"maximum":90},"longitude":{"type":"number","minimum":-180,"maximum":180},"is_headquarters":{"type":"boolean"}},"required":["external_id","name"],"additionalProperties":{}},"maxItems":100},"brokers":{"type":"array","items":{"type":"object","properties":{"external_id":{"type":"string","minLength":1,"maxLength":255},"name":{"type":"string","minLength":1,"maxLength":500},"email":{"type":"string","format":"email"},"phone":{"type":"string","maxLength":50},"photo":{"type":"string","format":"uri"},"title":{"type":"string","maxLength":200},"bio":{"type":"string","maxLength":10000},"specialization":{"type":"string","maxLength":200},"languages":{"type":"array","items":{"type":"string"}},"certifications":{"type":"array","items":{"type":"string"}},"year_started":{"type":"integer","minimum":1950,"maximum":2100},"branch_external_id":{"type":"string","maxLength":255},"linkedin":{"type":"string","format":"uri"},"instagram":{"type":"string","maxLength":200},"facebook":{"type":"string","format":"uri"},"website":{"type":"string","format":"uri"}},"required":["external_id","name"],"additionalProperties":{}},"maxItems":500},"properties":{"type":"array","items":{"type":"object","properties":{"external_id":{"type":"string","minLength":1,"maxLength":255},"title":{"type":"string","minLength":1,"maxLength":1000},"listing_type":{"type":"string","enum":["sale","rent","auction","shares","project"]},"category":{"type":"string","enum":["apartment","house","land","commercial","other"]},"city":{"type":"string","maxLength":200},"district":{"type":"string","maxLength":200},"street":{"type":"string","maxLength":500},"zip":{"type":"string","maxLength":20},"region":{"type":"string","maxLength":200},"country":{"type":"string","maxLength":5},"latitude":{"type":"number","minimum":-90,"maximum":90},"longitude":{"type":"number","minimum":-180,"maximum":180},"price":{"type":"number","minimum":0},"price_currency":{"type":"string","enum":["czk","eur","usd","gbp"]},"price_unit":{"type":"string","maxLength":50},"price_note":{"type":"string","maxLength":500},"area":{"type":"number","minimum":0},"land_area":{"type":"number","minimum":0},"floor_area":{"type":"number","minimum":0},"garden_area":{"type":"number","minimum":0},"balcony_area":{"type":"number","minimum":0},"terrace_area":{"type":"number","minimum":0},"cellar_area":{"type":"number","minimum":0},"subtype":{"type":"string","maxLength":100},"rooms_label":{"type":"string","maxLength":50},"summary":{"type":"string","maxLength":1000},"description":{"type":"string","maxLength":50000},"condition":{"type":"string","maxLength":50},"ownership":{"type":"string","maxLength":50},"furnishing":{"type":"string","maxLength":50},"energy_rating":{"type":"string","enum":["A","B","C","D","E","F","G"]},"building_material":{"type":"string","maxLength":50},"floor":{"type":"integer"},"total_floors":{"type":"integer"},"year_built":{"type":"integer","minimum":1000,"maximum":2100},"balcony":{"type":"boolean"},"terrace":{"type":"boolean"},"garden":{"type":"boolean"},"elevator":{"type":"boolean"},"cellar":{"type":"boolean"},"garage":{"type":"boolean"},"pool":{"type":"boolean"},"parking":{"type":"string","maxLength":50},"parking_spaces":{"type":"integer","minimum":0},"images":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"title":{"type":"string","maxLength":200},"order":{"type":"integer","minimum":0}},"required":["url"]},"maxItems":50},"matterport_url":{"type":"string","format":"uri"},"broker_external_id":{"type":"string","maxLength":255},"active":{"type":"boolean","default":true}},"required":["external_id","title","listing_type","category"],"additionalProperties":{}},"maxItems":500},"projects":{"type":"array","items":{"type":"object","properties":{"external_id":{"type":"string","minLength":1,"maxLength":255},"name":{"type":"string","minLength":1,"maxLength":500},"summary":{"type":"string","maxLength":2000},"description":{"type":"string","maxLength":50000},"city":{"type":"string","minLength":1,"maxLength":200},"location_label":{"type":"string","maxLength":2000},"district":{"type":"string","maxLength":200},"street":{"type":"string","maxLength":500},"zip":{"type":"string","maxLength":20},"region":{"type":"string","maxLength":200},"latitude":{"type":"number","minimum":-90,"maximum":90},"longitude":{"type":"number","minimum":-180,"maximum":180},"developer_name":{"type":"string","maxLength":500},"developer_ico":{"type":"integer","minimum":0},"project_lifetime":{"type":"integer","minimum":1,"maximum":3},"ceilings":{"type":"integer"},"doors":{"type":"integer"},"facade_coats":{"type":"integer"},"floors_codebook":{"type":"integer"},"foundations":{"type":"string","maxLength":100},"interior_plasters":{"type":"integer"},"interior_staircase":{"type":"integer"},"interior_wall_lining":{"type":"string","maxLength":100},"kitchen_cabinets":{"type":"integer"},"reinforced_concrete_staircase":{"type":"integer"},"roof":{"type":"integer"},"roofing":{"type":"integer"},"sheetmetal_structure":{"type":"integer"},"siding":{"type":"integer"},"windows":{"type":"integer"},"construction_start":{"type":"string","format":"date-time"},"estimated_completion":{"type":"string","format":"date-time"},"actual_completion":{"type":"string","format":"date-time"},"date_move":{"type":"string","format":"date-time"},"date_sale":{"type":"string","format":"date-time"},"project_url":{"type":"string","format":"uri"},"matterport_url":{"type":"string","format":"uri"},"mapy_panorama_url":{"type":"string","format":"uri"},"status":{"type":"string","enum":["planned","active","construction","selling","completed","archived"]},"agency_external_id":{"type":"string","maxLength":255},"broker_external_id":{"type":"string","maxLength":255},"images":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"url_thumbnail":{"type":"string","format":"uri"},"sort_order":{"type":"integer","minimum":0},"is_main":{"type":"boolean"},"alt_text":{"type":"string","maxLength":500},"photo_kind":{"type":"string","enum":["photo","floor_plan"],"default":"photo"}},"required":["url"]},"maxItems":100},"active":{"type":"boolean","default":true}},"required":["external_id","name","city"],"additionalProperties":{}},"maxItems":200},"external_source":{"type":"string","minLength":1,"maxLength":100,"default":"api"},"deactivate_missing":{"type":"boolean","default":false},"callback_url":{"type":"string","format":"uri"}}}}}},"responses":{"202":{"description":"Job enqueued","content":{"application/json":{"schema":{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending"]},"total_items":{"type":"integer"},"poll_url":{"type":"string"}},"required":["job_id","status","total_items","poll_url"]}}}},"400":{"description":"Invalid payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Missing write:import scope or not agency key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/import/jobs":{"get":{"summary":"List recent import jobs","tags":["Import"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","processing","completed","failed"]},"external_source":{"type":"string"},"total_items":{"type":"integer"},"completed_items":{"type":"integer"},"failed_items":{"type":"integer"},"warned_items":{"type":"integer"},"skipped_items":{"type":"integer"},"payload_summary":{"type":"object","additionalProperties":{"type":"integer"}},"created_at":{"type":"string"},"started_at":{"type":["string","null"]},"completed_at":{"type":["string","null"]},"items":{"type":"array","items":{"type":"object","properties":{"external_id":{"type":"string"},"entity_type":{"type":"string","enum":["agency","branch","broker","property","project"]},"status":{"type":"string","enum":["success","warning","error","skipped"]},"nemovizor_id":{"type":["string","null"],"format":"uuid"},"nemovizor_slug":{"type":["string","null"]},"action":{"type":["string","null"],"enum":["created","updated","unchanged","deactivated",null]},"warnings":{"type":"array","items":{"type":"string"}},"errors":{"type":"array","items":{"type":"string"}},"processing_time_ms":{"type":["integer","null"]}},"required":["external_id","entity_type","status","nemovizor_id","nemovizor_slug","action","warnings","errors","processing_time_ms"]}}},"required":["id","status","external_source","total_items","completed_items","failed_items","warned_items","skipped_items","created_at","started_at","completed_at"]}}},"required":["data"]}}}}}}},"/api/v1/import/jobs/{id}":{"get":{"summary":"Get import job status + per-item results","description":"Returns progress counters while processing. When completed, includes per-item results with warnings/errors.","tags":["Import"],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","processing","completed","failed"]},"external_source":{"type":"string"},"total_items":{"type":"integer"},"completed_items":{"type":"integer"},"failed_items":{"type":"integer"},"warned_items":{"type":"integer"},"skipped_items":{"type":"integer"},"payload_summary":{"type":"object","additionalProperties":{"type":"integer"}},"created_at":{"type":"string"},"started_at":{"type":["string","null"]},"completed_at":{"type":["string","null"]},"items":{"type":"array","items":{"type":"object","properties":{"external_id":{"type":"string"},"entity_type":{"type":"string","enum":["agency","branch","broker","property","project"]},"status":{"type":"string","enum":["success","warning","error","skipped"]},"nemovizor_id":{"type":["string","null"],"format":"uuid"},"nemovizor_slug":{"type":["string","null"]},"action":{"type":["string","null"],"enum":["created","updated","unchanged","deactivated",null]},"warnings":{"type":"array","items":{"type":"string"}},"errors":{"type":"array","items":{"type":"string"}},"processing_time_ms":{"type":["integer","null"]}},"required":["external_id","entity_type","status","nemovizor_id","nemovizor_slug","action","warnings","errors","processing_time_ms"]}}},"required":["id","status","external_source","total_items","completed_items","failed_items","warned_items","skipped_items","created_at","started_at","completed_at"]}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/import/properties/{external_id}":{"delete":{"summary":"Deactivate a property by external_id","description":"Synchronous single-property deactivation. Sets active=false on the matching property.","tags":["Import"],"parameters":[{"schema":{"type":"string"},"required":true,"name":"external_id","in":"path"}],"responses":{"200":{"description":"Deactivated","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"nemovizor_id":{"type":"string","format":"uuid"},"nemovizor_slug":{"type":"string"},"action":{"type":"string","enum":["deactivated"]}},"required":["ok","nemovizor_id","nemovizor_slug","action"]}}}},"404":{"description":"Property not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}}},"webhooks":{}}