Cyprus property data, one API
Homes for sale and rent from the main property sites in Cyprus, read every day, cleaned, and merged so one home is one property, however many agencies list it. Search offers, follow prices, read statistics by area, value a home from its comparables, and get webhooks when something new matches.
What you get
Listings
Search live offers by place, price, size, rooms, features or a shape on the map, newest first or by price. One row per home, or every offer.
Properties
The same home across sites and agencies as one property, with every offer and the full asking-price history.
Statistics
Asking-price levels, price per m², trends, yields and activity by district, town or quarter, or around a point.
Valuation
An estimate from comparable active offers, with a range, a confidence grade and the comparables it used.
Alerts
Saved searches that POST new offers and price changes to your webhook, signed, at once or once a day.
Plain data
Asking prices as published, never adjusted. A price no home can have (a night's rent, a rent posted as a sale) is left out of live searches and statistics; a doubtful size only loses its price per m².
Quick start
- Get a key. The API comes with the Pro and Enterprise plans; the Individual plan is for the website only. To try it first, ask for a free trial key at webrootdeveloper@gmail.com. Keep your key secret: send it from your server, never from a browser.
- Send it in the
X-API-Keyheader with every call. - Make a call. Place names work in Greek or English and in any common spelling.
Find two-bedroom flats for sale in Germasogeia under €400,000
curl -G "https://api.fimacode.com/api/v1/listings" \
-H "X-API-Key: $FIMACODE_KEY" \
-d purpose=sale -d property_type=Apartment \
-d municipality=Germasogeia \
-d min_bedrooms=2 -d max_bedrooms=2 \
-d max_price=400000
Value a 90 m² flat in Strovolos
curl -G "https://api.fimacode.com/api/v1/valuation" \
-H "X-API-Key: $FIMACODE_KEY" \
-d purpose=sale -d property_type=Apartment \
-d municipality=Strovolos \
-d sqm=90 -d bedrooms=2
Median asking prices per town in Limassol, in Python
import os, requests
answer = requests.get(
"https://api.fimacode.com/api/v1/stats/areas",
params={"purpose": "sale", "district": "Limassol", "group_by": "municipality"},
headers={"X-API-Key": os.environ["FIMACODE_KEY"]},
timeout=30,
)
answer.raise_for_status()
for area in answer.json()["data"]:
print(area["municipality"], area["median_price"], area["properties"])
How it behaves
Every answer has one shape
{"success": true, "data": …, "meta": {…}}. An error is success: false with the reason in data; a place name we do not know answers 422 with the names you likely meant.
Pages
Lists return up to per_page rows (100 at most). Pass meta.next_cursor back as cursor for the next page; the cursor never skips or repeats a row while offers change, and it reads to the end of the search, so a full sync is one loop.
Keeping a full copy in sync uses status=any or removed, updated_since and sort=updated_asc; these come with Pro and Enterprise and answer 403 on a trial key, which searches live offers with every other filter, including price_changed_since.
Limits
The API comes with the Pro and Enterprise plans; the Individual plan is for the website only, and its key answers 403 here. A trial key lets you try every call first, with small limits and without full sync.
Each plan has a per-minute and a daily allowance, shown on every answer in X-RateLimit-* and X-Quota-* headers. Past it you get 429 with Retry-After. The day resets at 00:00 UTC.
Each plan also reads a number of listings a day: every row of a search counts, and one listing or one home counts as one. The headers X-Rows-Limit and X-Rows-Remaining show where you stand; a page is cut short at the limit, and past it you get 429 until the day resets. Statistics and valuations do not count rows.
| Plan | Price | Requests per day | Listings per day | Requests per minute | Alerts | Full sync |
|---|---|---|---|---|---|---|
| Trial | Free, on request | 100 | 1,000 | 10 | – | – |
| Pro | €249 a month | 50,000 | 50,000 | 300 | 50 | Yes |
| Enterprise | from €900 a month | Unlimited | Unlimited | 1,000 | 200 | Yes |
Caching
Answers carry an ETag; send it back in If-None-Match and an unchanged answer comes back as 304 without its body.
Webhook alerts
Create an alert with POST /api/v1/alerts: a name, an https webhook_url, delivery (immediate or daily) and any listing filters. The answer holds webhook_secret once; store it.
Each delivery is a JSON POST signed with HMAC-SHA256 of timestamp.body. Check it before trusting the body:
import hashlib, hmac
def is_ours(secret: str, headers: dict, body: bytes) -> bool:
timestamp = headers["X-Alert-Timestamp"]
expected = hmac.new(secret.encode(), timestamp.encode() + b"." + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(headers["X-Alert-Signature"], f"sha256={expected}")
Answer with any 2xx. To try your endpoint now, POST /api/v1/alerts/{id}/test sends the three newest matches marked "test": true and tells you whether your webhook took them.
Reference
Every endpoint, parameter and field: interactive reference · readable reference · OpenAPI schema to generate a client.