Fimacode API

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

  1. 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.
  2. Send it in the X-API-Key header with every call.
  3. 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.

PlanPriceRequests per dayListings per dayRequests per minuteAlertsFull 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.