{"openapi":"3.1.0","info":{"title":"Gouna Realty Agent API","version":"1.1.0","description":"Agent-friendly read + lead-capture endpoints for El Gouna (Egypt) real-estate listings. **Aggregator, not a brokerage** — listings are sourced from 6 verified third-party platforms (Property Finder, Bayut, Aqarmap, Nawy, Dubizzle, Gouna360). 1884 properties baseline (Phase 1). Designed for ChatGPT plugins, Anthropic Claude tool-use, Perplexity, Gemini, and MCP-bridge clients. Every property URL deep-links back to the canonical /property/<slug> page so agents can cite their source. Lead submission requires explicit GDPR Art. 7 consent and routes the lead to the listing-owning agency.","contact":{"name":"Gouna Realty","email":"hello@gounarealty.com","url":"https://gounarealty.com/about"},"license":{"name":"CC-BY-4.0 (public read API)","url":"https://creativecommons.org/licenses/by/4.0/"},"x-aggregator-disclosure":"Gouna Realty is an aggregator of public real-estate listings; we are not the listing broker. Verify listing details with the source platform before relying on them.","x-source-platforms":["property_finder","bayut","aqarmap","nawy","dubizzle","gouna360"],"x-phase":"1"},"servers":[{"url":"https://gounarealty.com","description":"Production / preview (injected at request time)"}],"security":[{"bearerAuth":[]},{}],"tags":[{"name":"agent","description":"Public agent-facing endpoints, AI-crawler discoverable, tagged with `x-bot-accessible: true`."},{"name":"listings","description":"Property listing search + availability."},{"name":"stats","description":"Per-neighborhood aggregate statistics + yield estimates."},{"name":"facts","description":"Static market reference data (laws, taxes, yields)."},{"name":"sources","description":"Data-provenance + freshness metadata for citation transparency."},{"name":"leads","description":"Buyer/renter lead intake with GDPR-consent gate."}],"paths":{"/api/agent/search":{"post":{"operationId":"search_listings","summary":"Search El Gouna property listings","description":"Filter the live aggregated catalog by listing-type (sale/rent/stay), neighborhood, price range, bedrooms, and currency. Returns property summaries with absolute URLs back to the canonical /property/<slug> pages so agents can cite their source.","tags":["agent","listings"],"x-bot-accessible":true,"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchInput"}}}},"responses":{"200":{"description":"Matching property listings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}}},"400":{"description":"Invalid input (Zod validation failure)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (tiered: anonymous 10/min, free-tier 100/min, paid-tier 1000/min). See `Retry-After` header.","headers":{"Retry-After":{"description":"Seconds until the next request is permitted.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Reason":{"description":"Reason code (burst | sustained | tier-exhaust).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream data fetch failed (Supabase unavailable)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/agent/stats":{"get":{"operationId":"get_neighborhood_stats","summary":"Aggregate stats per El Gouna neighborhood","description":"Returns listing-count, price range, median, and estimated annual rental yield per El Gouna neighborhood. Omit `neighborhood` for all-neighborhood roll-up. Yield = (avg monthly rent USD * 12) / avg sale price USD * 100, computed only when both legs have data.","tags":["agent","stats"],"x-bot-accessible":true,"parameters":[{"name":"neighborhood","in":"query","required":false,"schema":{"type":"string","enum":["marina","downtown","west-golf","north-golf","south-marina","abu-tig","tawila","mangroovy"]},"description":"Neighborhood slug. Omit for all-neighborhoods response."},{"name":"currency","in":"query","required":false,"schema":{"type":"string","enum":["USD","EUR","EGP"],"default":"USD"},"description":"Currency for price aggregates (USD primary, EUR/EGP secondary)."}],"responses":{"200":{"description":"Neighborhood stats payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatsResponse"}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (tiered).","headers":{"Retry-After":{"description":"Seconds until the next request is permitted.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream data fetch failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/agent/facts":{"get":{"operationId":"get_market_facts","summary":"El Gouna real-estate market facts","description":"Static reference data covering foreign-ownership rules (Egyptian Law 230/1996), typical yields, capital appreciation, tax framework, closing timeline, currency context, and neighborhood inventory. Every claim is sourced and date-stamped (`lastReviewed`). Returned facts are reference information, NOT legal or financial advice.","tags":["agent","facts"],"x-bot-accessible":true,"responses":{"200":{"description":"Cited market facts payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactsResponse"}}}},"429":{"description":"Rate-limited (tiered).","headers":{"Retry-After":{"description":"Seconds until the next request is permitted.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/agent/availability":{"get":{"operationId":"check_availability","summary":"Confirm listing availability + broker contact","description":"Read-only check of whether a specific El Gouna listing is currently available. Returns status, listing-type, price snapshot, last-updated timestamp, and the broker's WhatsApp contact URL. Agents should call this right before `submit_lead` so a sold or reserved property never receives a new lead. Supports fuzzy-slug fallback (Levenshtein ≤2) so minor casing/punctuation drift in agent-supplied slugs still resolves.","tags":["agent","listings"],"x-bot-accessible":true,"parameters":[{"name":"slug","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":200},"description":"Listing slug (from `search_listings` response)."},{"name":"id","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":120},"description":"Listing UUID (alternative to slug)."}],"responses":{"200":{"description":"Availability + contact payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvailabilityResponse"}}}},"400":{"description":"Missing both `slug` and `id`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Property not found (after fuzzy-resolution)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (tiered).","headers":{"Retry-After":{"description":"Seconds until the next request is permitted.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Data source temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/agent/sources":{"get":{"operationId":"get_data_sources","summary":"Data provenance + freshness per source platform","description":"Per-source aggregate counts, freshness category, last-scrape timestamp, and Tier-SLA window. AI-agents quoting our listings MUST be able to disclose where the data came from and how recent it is. Freshness buckets: fresh <7d / stale 7-30d / archive >30d. Returns `mode: \"no-config\"` with an empty `sources` array when SUPABASE_SERVICE_ROLE_KEY is unavailable (honest zero-state, no mock data).","tags":["agent","sources"],"x-bot-accessible":true,"responses":{"200":{"description":"Data-provenance payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourcesResponse"}}}},"429":{"description":"Rate-limited (tiered).","headers":{"Retry-After":{"description":"Seconds until the next request is permitted.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Aggregation failed (Supabase unavailable)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/agent/lead":{"post":{"operationId":"submit_lead","summary":"Submit a buyer/renter lead on behalf of the user","description":"Persists a lead under the listing-owning agency (when `propertyId` or `propertySlug` is supplied) or under the platform tenant otherwise. Requires explicit GDPR Art. 7 consent (`consent: true` — literal). Server-side resolves the owning agency from the property — caller-supplied tenant/agency values are ignored to prevent cross-tenant PII leakage. Returns a pre-filled WhatsApp follow-up URL the agent surfaces back to the user. Either `email` OR `phone` is required.","tags":["agent","leads"],"x-bot-accessible":true,"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadInput"}}}},"responses":{"200":{"description":"Lead persisted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadResponse"}}}},"400":{"description":"Invalid input or property not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (tiered + in-memory burst limiter). Both limiters apply to lead submission to prevent spam.","headers":{"Retry-After":{"description":"Seconds until the next request is permitted.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Reason":{"description":"Reason code (burst | sustained | tier-exhaust).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Storage failure (Supabase + JSONL fallback both down)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"Opaque API-key Bearer token. Anonymous-tier (no token) is rate-limited to 10 req/min burst; free-tier 100 req/min; paid-tier 1000 req/min. Contact hello@gounarealty.com to provision a token."}},"schemas":{"SearchInput":{"type":"object","properties":{"type":{"type":"string","enum":["sale","rent","stay"]},"neighborhood":{"type":"string","enum":["marina","downtown","west-golf","north-golf","south-marina","abu-tig","tawila","mangroovy"]},"minPrice":{"type":"number","minimum":0},"maxPrice":{"type":"number","minimum":0},"currency":{"type":"string","enum":["USD","EUR","EGP"],"default":"USD"},"bedrooms":{"type":"integer","minimum":0},"limit":{"type":"integer","minimum":1,"maximum":50,"default":10}}},"Listing":{"type":"object","properties":{"slug":{"type":"string"},"url":{"type":"string","format":"uri"},"title":{"type":"string"},"type":{"type":"string","enum":["sale","rent","both"]},"neighborhood":{"type":"string","nullable":true},"bedrooms":{"type":"integer","nullable":true},"bathrooms":{"type":"integer","nullable":true},"sqm":{"type":"number","nullable":true},"price":{"type":"number","nullable":true},"currency":{"type":"string","enum":["USD","EUR","EGP"]},"furnished":{"type":"boolean","nullable":true},"photoUrl":{"type":"string","format":"uri","nullable":true},"propertyType":{"type":"string","enum":["villa","apartment","chalet","studio","penthouse","land"]}},"required":["slug","url","title","type","currency","propertyType"]},"SearchResponse":{"type":"object","properties":{"listings":{"type":"array","items":{"$ref":"#/components/schemas/Listing"}},"totalCount":{"type":"integer"},"queryUrl":{"type":"string","format":"uri"},"currency":{"type":"string"},"limit":{"type":"integer"}},"required":["listings","totalCount","queryUrl"]},"NeighborhoodStats":{"type":"object","properties":{"neighborhood":{"type":"string"},"slug":{"type":"string"},"totalListings":{"type":"integer"},"forSale":{"type":"integer"},"forRent":{"type":"integer"},"currency":{"type":"string"},"priceStats":{"type":"object","properties":{"count":{"type":"integer"},"avg":{"type":"number","nullable":true},"min":{"type":"number","nullable":true},"max":{"type":"number","nullable":true},"median":{"type":"number","nullable":true}}},"rentStats":{"type":"object","properties":{"count":{"type":"integer"},"avgMonthlyUsd":{"type":"number","nullable":true}}},"estimatedAnnualYieldPercent":{"type":"number","nullable":true}}},"StatsResponse":{"type":"object","properties":{"stats":{"type":"array","items":{"$ref":"#/components/schemas/NeighborhoodStats"}},"currency":{"type":"string"},"totalPublishedListings":{"type":"integer"},"generatedAt":{"type":"string","format":"date-time"}}},"FactsResponse":{"type":"object","properties":{"facts":{"type":"object","description":"Structured market-context object covering location, ownership law, yields, taxes, closing timeline, currency, neighborhoods, and disclaimer. Every claim is sourced + date-stamped via nested `sources[]` + `lastReviewed`."},"generatedAt":{"type":"string","format":"date-time"}}},"SourceProvenance":{"type":"object","properties":{"slug":{"type":"string","enum":["property_finder","bayut","aqarmap","nawy","dubizzle","gouna360"]},"displayName":{"type":"string"},"homepageUrl":{"type":"string","format":"uri"},"category":{"type":"string"},"tier":{"type":"integer","minimum":1,"maximum":8},"count":{"type":"integer","description":"Number of available+published listings."},"latestScrape":{"type":"string","format":"date-time","nullable":true},"freshness":{"type":"string","enum":["fresh","stale","archive","unknown"],"description":"fresh <7d / stale 7-30d / archive >30d / unknown."},"slaWindowDays":{"type":"integer","description":"Per-Tier scrape SLA window (1d Tier-1 ... 30d Tier-8)."},"stalePct":{"type":"number","description":"Fraction of listings older than the SLA window (0-1)."},"status":{"type":"string"}},"required":["slug","displayName","homepageUrl","tier","count","freshness","slaWindowDays"]},"SourcesResponse":{"type":"object","properties":{"generatedAt":{"type":"string","format":"date-time"},"mode":{"type":"string","enum":["ok","no-config","error"]},"totalListings":{"type":"integer"},"totalSources":{"type":"integer"},"sources":{"type":"array","items":{"$ref":"#/components/schemas/SourceProvenance"}},"disclaimer":{"type":"string","description":"Aggregator-not-broker disclosure surfaced in every response so agents can cite it verbatim."}},"required":["generatedAt","mode","sources","disclaimer"]},"AvailabilityResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"slug":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"},"available":{"type":"boolean"},"status":{"type":"string","enum":["available","reserved","sold","off-market"]},"listingType":{"type":"string","enum":["sale","rent","both"]},"propertyType":{"type":"string","enum":["villa","apartment","chalet","studio","penthouse","land"]},"neighborhood":{"type":"string","nullable":true},"bedrooms":{"type":"integer","nullable":true},"bathrooms":{"type":"integer","nullable":true},"sqm":{"type":"number","nullable":true},"furnished":{"type":"boolean","nullable":true},"price":{"type":"object","properties":{"usd":{"type":"number","nullable":true},"eur":{"type":"number","nullable":true},"egp":{"type":"number","nullable":true},"rentMonthlyUsd":{"type":"number","nullable":true}}},"updatedAt":{"type":"string","format":"date-time"},"listingUrl":{"type":"string","format":"uri"},"contact":{"type":"object","properties":{"method":{"type":"string","enum":["whatsapp"]},"url":{"type":"string","format":"uri"},"responseTimeHours":{"type":"number"},"leadEndpoint":{"type":"string","format":"uri"}}}},"required":["success","slug","id","available","status"]},"LeadInput":{"type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":100},"email":{"type":"string","format":"email","maxLength":200},"phone":{"type":"string","minLength":8,"maxLength":20},"propertySlug":{"type":"string","maxLength":200},"propertyId":{"type":"string","maxLength":120},"message":{"type":"string","minLength":10,"maxLength":2000},"agent":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"sessionId":{"type":"string","maxLength":200}},"required":["name"]},"consent":{"type":"boolean","enum":[true],"description":"Must be literal `true` (GDPR Art. 7 explicit opt-in)."},"locale":{"type":"string","enum":["en","ar","ru","de","nl"],"default":"en"}},"required":["name","message","agent","consent"]},"LeadResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"message":{"type":"string"},"leadId":{"type":"string","format":"uuid"},"storage":{"type":"string","enum":["supabase","jsonl"]},"followUpUrl":{"type":"string","format":"uri"},"propertyUrl":{"type":"string","format":"uri"},"agentSessionId":{"type":"string"}},"required":["success","message"]},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[false]},"error":{"type":"string"},"message":{"type":"string"},"details":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}}}}]}},"required":["error"]}}}}