{"openapi":"3.1.0","info":{"title":"FL Brokers listings","description":"Commercial real-estate listings from FL Brokers, collected from the Kenlo public API and served read-only. The record contract is `/schema.json`; this document describes the HTTP surface around it.\n\n**The portfolio is commercial, not residential** — 78,5% is office floor. And it is **incomplete on purpose-of-record**: 9.980 of the 11.243 the source declares, because Kenlo's Elasticsearch index stops deep pagination at 10.000. `/api/health` reports the gap rather than hiding it.","version":"0.1.0"},"paths":{"/schema.json":{"get":{"tags":["contract"],"summary":"The record contract","description":"JSON Schema of a listing, measured against the real payload — including the quirks we chose to describe rather than fix: the 1-cent 'price on request' sentinel, the 5.022.800 m² outlier, and the `property_metreage` field that says SQFT on a listing advertised in m².","operationId":"schema_schema_json_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Schema Schema Json Get"}}}}}}},"/api/health":{"get":{"tags":["meta"],"summary":"Freshness, not liveness","description":"How many listings are loaded, when they were collected, and how far that is from what the source declares. A process answering 200 over a month-old database is the failure this endpoint exists to make visible.","operationId":"health_api_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Api Health Get"}}}}}}},"/api/facets":{"get":{"tags":["meta"],"summary":"Counts to build a filter with","description":"City, state, property type and purpose, each from a GROUP BY — never a constant.","operationId":"facets_api_facets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Facets Api Facets Get"}}}}}}},"/api/listings":{"get":{"tags":["listings"],"summary":"Search the listings","description":"Every filter is optional and combines with AND. Money is in **cents**, integer — the client formats. Sorting accepts `ref`, `sale`, `rent`, `area` and `collected_at`.","operationId":"listings_api_listings_get","parameters":[{"name":"city","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact city, case-insensitive.","examples":["São Paulo"],"title":"City"},"description":"Exact city, case-insensitive."},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state.","examples":["SP"],"title":"State"},"description":"Two-letter state."},{"name":"neighborhood","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact neighborhood, case-insensitive.","title":"Neighborhood"},"description":"Exact neighborhood, case-insensitive."},{"name":"property_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Kenlo's own type, untranslated.","examples":["OFFICE_FLOOR"],"title":"Property Type"},"description":"Kenlo's own type, untranslated."},{"name":"purpose","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"FOR_SALE or FOR_RENT. A listing is commonly both.","examples":["FOR_SALE"],"title":"Purpose"},"description":"FOR_SALE or FOR_RENT. A listing is commonly both."},{"name":"sale_cents_gte","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Minimum sale price, in CENTS.","title":"Sale Cents Gte"},"description":"Minimum sale price, in CENTS."},{"name":"sale_cents_lte","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Maximum sale price, in CENTS.","title":"Sale Cents Lte"},"description":"Maximum sale price, in CENTS."},{"name":"rent_cents_gte","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Minimum monthly rent, in CENTS.","title":"Rent Cents Gte"},"description":"Minimum monthly rent, in CENTS."},{"name":"rent_cents_lte","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Maximum monthly rent, in CENTS.","title":"Rent Cents Lte"},"description":"Maximum monthly rent, in CENTS."},{"name":"area_m2_gte","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Minimum area in m².","title":"Area M2 Gte"},"description":"Minimum area in m²."},{"name":"area_m2_lte","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Maximum area in m².","title":"Area M2 Lte"},"description":"Maximum area in m²."},{"name":"bedrooms_gte","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Minimum bedrooms.","title":"Bedrooms Gte"},"description":"Minimum bedrooms."},{"name":"parking_spaces_gte","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Minimum parking spaces.","title":"Parking Spaces Gte"},"description":"Minimum parking spaces."},{"name":"sort","in":"query","required":false,"schema":{"type":"string","description":"ref | sale | rent | area | collected_at","default":"ref","title":"Sort"},"description":"ref | sale | rent | area | collected_at"},{"name":"desc","in":"query","required":false,"schema":{"type":"boolean","description":"Descending order.","default":false,"title":"Desc"},"description":"Descending order."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Page size, at most 200.","default":20,"title":"Limit"},"description":"Page size, at most 200."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Rows to skip.","default":0,"title":"Offset"},"description":"Rows to skip."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Listings Api Listings Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/listings/{ref}":{"get":{"tags":["listings"],"summary":"One listing by its reference","description":"`ref` is the Kenlo reference and the public identifier. 404 when it does not exist.","operationId":"listing_api_listings__ref__get","parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string","title":"Ref"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Listing Api Listings  Ref  Get"}}}},"404":{"description":"No listing carries that reference."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/listings/{ref}/raw":{"get":{"tags":["listings"],"summary":"The Kenlo payload, untouched","description":"Exists so nobody has to ask for a migration to read a field we did not normalize — the photo URLs, the amenity codes and `property_metreage` live only here.","operationId":"listing_raw_api_listings__ref__raw_get","parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string","title":"Ref"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Listing Raw Api Listings  Ref  Raw Get"}}}},"404":{"description":"No listing carries that reference."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ingest":{"post":{"tags":["meta"],"summary":"Ask for a fresh collection, in the background","description":"Answers **202 immediately** and collects afterwards, on FastAPI's own BackgroundTasks. No queue, no worker, no Redis — and therefore: no progress (watch `/api/health`), no retry (a collection that dies, dies quietly), and nothing survives a restart. A second call while one runs gets **409**.","operationId":"start_ingest_api_ingest_post","parameters":[{"name":"host","in":"query","required":false,"schema":{"type":"string","description":"Kenlo host to collect from.","default":"www.flbrokers.com.br","title":"Host"},"description":"Kenlo host to collect from."},{"name":"site_id","in":"query","required":false,"schema":{"type":"integer","description":"Kenlo site id.","default":1710,"title":"Site Id"},"description":"Kenlo site id."},{"name":"page_limit","in":"query","required":false,"schema":{"type":"integer","maximum":400,"minimum":1,"description":"Pages at most. The source's own ceiling is 209.","default":209,"title":"Page Limit"},"description":"Pages at most. The source's own ceiling is 209."}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"409":{"description":"A collection is already running."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}