{"openapi":"3.0.3","info":{"title":"PlusHouse API","description":"Public REST API for PlusHouse — the Balkans property marketplace. Browse listings and agencies. All endpoints are publicly reachable without authentication unless noted. Use this spec for LLM function calling and agent integrations.","version":"1.0.0","contact":{"name":"PlusHouse Support","email":"support@plushouse.eu","url":"https://plushouse.eu/contact"},"license":{"name":"Proprietary","url":"https://plushouse.eu/terms"}},"servers":[{"url":"https://plushouse.eu","description":"Production"},{"url":"http://localhost:3000","description":"Local development"}],"tags":[{"name":"Health","description":"Service health"},{"name":"Listings","description":"Property listings"},{"name":"Agencies","description":"Real estate agencies and businesses"},{"name":"Inquiries","description":"Contact / lead capture"}],"paths":{"/api/health":{"get":{"operationId":"getHealth","summary":"Health check","description":"Returns service health and version. No authentication required. Use to verify the API is reachable before calling other endpoints.","tags":["Health"],"responses":{"200":{"description":"Service is healthy","content":{"application/json":{"schema":{"type":"object","required":["status","service","timestamp"],"properties":{"status":{"type":"string","example":"ok"},"service":{"type":"string","example":"plushouse"},"timestamp":{"type":"string","format":"date-time"},"version":{"type":"string","example":"1.0.0"}}}}}}}}},"/api/listings":{"get":{"operationId":"listListings","summary":"List property listings","description":"Returns a limited list of approved property listings. Filter by country, city, category, listing type, price, and more. Public, no auth. Collection filters apply to a bounded recent inventory sample. Returns a limited snapshot, without cursor or page traversal; an empty sample is not evidence that a market has no matching inventory.","tags":["Listings"],"parameters":[{"name":"limit","in":"query","description":"Number of results (1-60, default 10)","schema":{"type":"integer","minimum":1,"maximum":60,"default":10}},{"name":"country","in":"query","description":"Supported country ISO code or name (e.g. XK/Kosovo, AL/Albania)","schema":{"type":"string","example":"XK"}},{"name":"city","in":"query","description":"City name (e.g. Prishtina, Tirana)","schema":{"type":"string","example":"Prishtina"}},{"name":"category","in":"query","description":"Category slug: apartments, houses, land, commercial","schema":{"type":"string","enum":["apartments","houses","land","commercial"]}},{"name":"listingType","in":"query","description":"sale or rent","schema":{"type":"string","enum":["sale","rent"]}},{"name":"minPrice","in":"query","description":"Minimum price in EUR","schema":{"type":"number","minimum":0}},{"name":"maxPrice","in":"query","description":"Maximum price in EUR","schema":{"type":"number","minimum":0}},{"name":"bedrooms","in":"query","description":"Minimum bedrooms","schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"Limited recent listings snapshot","content":{"application/json":{"schema":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ListingCard"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/listings/{id}":{"get":{"operationId":"getListingById","summary":"Get a listing by ID","description":"Returns a single listing with full details, media, and agency info. Public, no auth. Returns 404 with JSON error if not found.","tags":["Listings"],"parameters":[{"name":"id","in":"path","required":true,"description":"Listing ID (Convex ID)","schema":{"type":"string"}}],"responses":{"200":{"description":"Listing details","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ListingDetail"}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/agencies":{"get":{"operationId":"listAgencies","summary":"List agencies / businesses","description":"Returns a limited list of approved agencies and service businesses. No cursor or page traversal is available. Filter by country, city, type. Public, no auth.","tags":["Agencies"],"parameters":[{"name":"limit","in":"query","description":"Number of results (1-50, default 10)","schema":{"type":"integer","minimum":1,"maximum":50,"default":10}},{"name":"country","in":"query","description":"Supported country ISO code or name","schema":{"type":"string","example":"XK"}},{"name":"city","in":"query","description":"City name","schema":{"type":"string"}},{"name":"businessType","in":"query","description":"agency or service","schema":{"type":"string","enum":["agency","service"]}}],"responses":{"200":{"description":"Limited agencies snapshot","content":{"application/json":{"schema":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AgencyCard"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/agencies/{id}":{"get":{"operationId":"getAgencyById","summary":"Get an agency by ID","description":"Returns a single agency/business with details. Public, no auth.","tags":["Agencies"],"parameters":[{"name":"id","in":"path","required":true,"description":"Business ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Agency details","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AgencyDetail"}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/inquiries/guest":{"post":{"operationId":"createGuestInquiry","summary":"Send an inquiry as a guest","description":"Creates a contact inquiry for an approved listing without authentication. Rate-limited by IP. Requires listingId, phone, message.","tags":["Inquiries"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["listingId","phone","message"],"properties":{"listingId":{"type":"string","description":"Listing ID"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"phone":{"type":"string","description":"Contact phone"},"message":{"type":"string","description":"Message body"}}}}}},"responses":{"200":{"description":"Inquiry created","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"inquiryId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}}}}},"components":{"schemas":{"Pagination":{"type":"object","required":["limit","count"],"properties":{"limit":{"type":"integer"},"count":{"type":"integer"},"hasMore":{"type":"boolean","description":"True when the response reached its limit. Does not guarantee another page exists; cursor/page traversal is not supported."}}},"ListingCard":{"type":"object","required":["_id","title","price","city","country","listingType"],"properties":{"_id":{"type":"string","description":"Convex document ID"},"title":{"type":"string","example":"2-Bedroom Apartment in Prishtina"},"titleTranslations":{"type":"object","properties":{"en":{"type":"string"},"de":{"type":"string"},"sq":{"type":"string"},"sr":{"type":"string"},"el":{"type":"string"},"mk":{"type":"string"}}},"price":{"type":"number","example":120000},"listingType":{"type":"string","enum":["sale","rent"]},"country":{"type":"string","example":"XK"},"city":{"type":"string","example":"Prishtina"},"bedrooms":{"type":"integer","nullable":true},"bathrooms":{"type":"integer","nullable":true},"area":{"type":"number","nullable":true,"description":"Area in m²"},"primaryImage":{"type":"string","nullable":true,"format":"uri"},"images":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","format":"uri"}}}},"createdAt":{"type":"integer","description":"Unix ms timestamp"}}},"ListingDetail":{"allOf":[{"$ref":"#/components/schemas/ListingCard"},{"type":"object","properties":{"description":{"type":"string"},"address":{"type":"string","nullable":true},"lat":{"type":"number","nullable":true},"lng":{"type":"number","nullable":true},"business":{"type":"object","nullable":true,"properties":{"_id":{"type":"string"},"name":{"type":"object"},"city":{"type":"string","nullable":true},"logo":{"type":"string","nullable":true,"format":"uri"}}},"viewCount":{"type":"integer"},"status":{"type":"string","enum":["pending","approved","rejected","suspended"]}}}]},"AgencyCard":{"type":"object","required":["_id","name","status"],"properties":{"_id":{"type":"string"},"name":{"type":"object","required":["en"],"properties":{"en":{"type":"string"},"de":{"type":"string"},"sq":{"type":"string"},"sr":{"type":"string"},"el":{"type":"string"},"mk":{"type":"string"}}},"city":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"logo":{"type":"string","nullable":true,"format":"uri"},"coverImage":{"type":"string","nullable":true,"format":"uri"},"businessType":{"type":"string","enum":["agency","service"]},"status":{"type":"string","enum":["pending","approved","rejected"]},"isFeatured":{"type":"boolean"}}},"AgencyDetail":{"allOf":[{"$ref":"#/components/schemas/AgencyCard"},{"type":"object","properties":{"description":{"type":"object","nullable":true},"email":{"type":"string","format":"email"},"phone":{"type":"string","nullable":true},"website":{"type":"string","nullable":true,"format":"uri"},"address":{"type":"string","nullable":true},"listingCount":{"type":"integer"}}}]},"Error":{"type":"object","required":["error","code","message"],"properties":{"error":{"type":"string","example":"Not Found"},"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"The requested resource was not found."},"hint":{"type":"string","example":"Check the ID or see /docs for valid endpoints."},"details":{"type":"object","nullable":true}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalError":{"description":"A backend error occurred. Retry later; a failure is not an empty inventory result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limited. Wait for Retry-After seconds before retrying.","headers":{"Retry-After":{"description":"Seconds until the current rate-limit window resets","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}