WoningMe Developer API

Stored only in this browser. Never sent anywhere except the requests you send below.

Getting started

The WoningMe Developer API gives you programmatic access to rental listings across the Netherlands, both private-sector and housing-association/public-sector, scoped to search filters you define, with optional webhook notifications when new listings match.

  1. Create a developer account. The same account works for the WoningMe app.
  2. Open your dashboard and click "Generate API key". Copy it immediately, it's shown once.
  3. Create a search filter (below) and start querying matching rental properties, or add a webhook_url to get notified automatically.

A machine-readable OpenAPI spec is also available, for client generators, Postman/Insomnia import, and similar tooling.

Every developer account gets full access free of charge until September 15, 2026. After that date, continued access requires an active subscription.

Authentication

Every request must include your API key in the X-Api-Key header:

curl https://woning.me/api/developer/v1/searches \
  -H "X-Api-Key: YOUR_API_KEY"

Requests are rate limited to 60 per minute per API key. Exceeding this returns 429.

Search filters

A search filter defines a city, radius, housing segments, and optional price/rooms/surface minimums/maximums. You can't query rental properties directly: every property lookup and webhook is scoped to a search filter you've created first. The endpoint path is /searches; "search filter" is just what we call it in these docs.

Creates a new search filter. webhook_url is optional. Omit it if you only want to query rental properties.

By default a new search filter matches all three housing segments (private-sector, housing-association, and mid-market rent). Set is_free_market / is_social_housing / is_middenhuur explicitly to narrow it to just the segment(s) you want, for example is_social_housing: true alone returns only housing-association listings.

FieldTypeRequiredNotes
labelstringyesA name you choose for this search filter.
citystringyesDutch city or municipality name.
radius_kmnumberyesSearch radius in km from the city center.
is_free_marketbooleannoInclude private-sector (vrije sector) listings. Segment flags are independent; see note above.
is_social_housingbooleannoInclude housing-association / public-sector (corporatie, sociale huur) listings.
is_middenhuurbooleannoInclude mid-market rent (middenhuur) listings.
min_priceintegernoMinimum monthly rent in whole euros. Omit for no minimum.
max_priceintegernoMaximum monthly rent in whole euros. Omit for no maximum.
min_roomsintegernoMinimum number of rooms. Omit for no minimum.
min_surfaceintegernoMinimum floor area in square meters. Omit for no minimum.
webhook_urlstringnoHTTPS URL to receive a POST when a new listing matches. Omit for no webhook.
curl -X POST https://woning.me/api/developer/v1/searches \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label": "Amsterdam centrum", "city": "Amsterdam", "radius_km": 5.0, "is_social_housing": true, "min_price": 700, "webhook_url": "https://your-app.com/hooks/amsterdam"}'

201 Created, with a Location header pointing at the new search filter:

{
  "id": 42,
  "label": "Amsterdam centrum",
  "city": "Amsterdam",
  "radiusKm": 5.0,
  "createdAt": "2026-07-27T20:00:00+00:00",
  "propertiesUrl": "/api/developer/v1/searches/42/properties",
  "isFreeMarket": false,
  "isSocialHousing": true,
  "isMiddenhuur": false,
  "minPrice": 700,
  "maxPrice": null,
  "minRooms": null,
  "minSurface": null,
  "webhookUrl": "https://your-app.com/hooks/amsterdam",
  "webhookSecret": "a3f9c2e1..."
}

maxPrice, minRooms, and minSurface are null when no filter was set for that field. webhookSecret is generated the first time you set a webhook_url and stays the same across later changes to the URL. See Webhooks for how to use it.

GET /api/developer/v1/searches

Lists all of your search filters.

curl https://woning.me/api/developer/v1/searches \
  -H "X-Api-Key: YOUR_API_KEY"
{
  "searches": [
    { "id": 42, "label": "Amsterdam centrum", "city": "Amsterdam", "radiusKm": 5.0, ... }
  ]
}
Try it

Fetches a single search filter by ID. Returns 404 if it doesn't exist.

curl https://woning.me/api/developer/v1/searches/42 \
  -H "X-Api-Key: YOUR_API_KEY"

Updates a search filter. Only fields you include are changed. Omit a field to leave it as-is. Send an empty string for webhook_url to remove the webhook (this also clears the secret).

There is currently no way to clear a price/rooms/surface filter back to unset via PATCH once set; delete and recreate the search filter instead.

curl -X PATCH https://woning.me/api/developer/v1/searches/42 \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"radius_km": 7.5}'

Deletes a search filter. Returns 204 No Content.

curl -X DELETE https://woning.me/api/developer/v1/searches/42 \
  -H "X-Api-Key: YOUR_API_KEY"

Rental properties

GET /api/developer/v1/searches/{id}/properties

Returns rental properties matching that search filter's city, radius, and segments, further narrowed by any of the query params below. All query params are optional and combine with each other and with the search filter, for example ?source=nederwoon&property_type=apartment&max_price=1500 returns only Nederwoon apartments at or under €1,500. The geographic and segment scope always comes from the search filter and can't be widened per request; min_price/max_price/min_rooms/min_surface can only narrow the search filter's own values further, not loosen them.

Query paramDefaultNotes
sourcenonefilter to one source, e.g. nederwoon
property_typenonefilter to one type, e.g. apartment, house, studio, room
min_pricenonewhole euros; combines with the search filter's own min_price, the stricter of the two wins
max_pricenonewhole euros; combines with the search filter's own max_price, the stricter of the two wins
min_roomsnonecombines with the search filter's own min_rooms, the stricter of the two wins
min_surfacenonemΒ²; combines with the search filter's own min_surface, the stricter of the two wins
sortpriceprice, surface, rooms, available, city, last_synced_at, first_seen_at
orderascasc or desc
skip0for pagination
limit200max 1000
curl "https://woning.me/api/developer/v1/searches/42/properties?source=nederwoon&property_type=apartment&max_price=1500" \
  -H "X-Api-Key: YOUR_API_KEY"
{
  "total": 274,
  "skip": 0,
  "limit": 2,
  "properties": [
    {
      "id": 19022,
      "source": "nederwoon",
      "sourceLabel": "Nederwoon",
      "url": "https://...",
      "imageUrl": "https://...",
      "street": "Kerkstraat 12",
      "postcode": "1234AB",
      "city": "Amsterdam",
      "propertyType": "Apartment",
      "constructionType": null,
      "surfaceM2": 75,
      "rooms": 3,
      "availableFrom": null,
      "priceEurCents": 145000,
      "priceText": "€ 1.450",
      "reactions": null,
      "agent": "Some Agency",
      "agentWebsite": "https://...",
      "agentPhone": null,
      "socialHousing": false,
      "isHousingAssociation": false,
      "isMiddenhuur": false,
      "assignmentType": null,
      "neighbourhoodScore": { "score": 6.8, "class": "ruim voldoende", "year": 2024, "dimensions": {...} },
      "lat": 52.37,
      "lon": 4.90,
      "firstSeenAt": "2026-07-27T20:20:00+00:00",
      "lastSyncedAt": "2026-07-27T20:20:00+00:00"
    }
  ]
}

Many fields are nullable, since sources don't all publish the same data: constructionType, availableFrom, reactions, agentWebsite, agentPhone, and assignmentType are frequently null; imageUrl, street, postcode, surfaceM2, rooms, priceEurCents, agent, lat, and lon are null when the source didn't publish that field or a listing couldn't be geocoded; neighbourhoodScore is null when unavailable for that postcode.

Try it

Webhooks

If a search filter has a webhook_url, we send an HTTP POST to it whenever a new listing matches. No polling required.

Payload

POST https://your-app.com/hooks/amsterdam
Content-Type: application/json
X-WoningMe-Signature: sha256=<hex digest>

{
  "event": "listing.new",
  "searchId": 42,
  "property": { /* same shape as the Rental properties endpoint above */ }
}

Verifying the signature

The X-WoningMe-Signature header is an HMAC-SHA256 digest of the raw request body, using your search filter's webhookSecret (returned when you create or fetch the search filter) as the key. Always verify it before trusting a payload.

# Python
import hashlib, hmac

def verify(request_body: bytes, signature_header: str, webhook_secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        webhook_secret.encode(), request_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)
// Node.js
const crypto = require("crypto");

function verify(rawBody, signatureHeader, webhookSecret) {
  const expected = "sha256=" + crypto
    .createHmac("sha256", webhookSecret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}

Retries

If your endpoint doesn't respond with a 2xx status (or times out), we retry once after about 30 seconds, then give up. Check delivery history to see what happened.

Webhook delivery stops automatically once your free developer access expires (see Getting started). This is independent of any WoningMe Plus subscription on your account.

Deliveries

GET /api/developer/v1/searches/{id}/deliveries

Returns the recent webhook delivery attempts for a search filter. Useful for debugging your endpoint.

curl https://woning.me/api/developer/v1/searches/42/deliveries \
  -H "X-Api-Key: YOUR_API_KEY"
{
  "deliveries": [
    {
      "id": 501,
      "propertyId": 19022,
      "status": "success",
      "httpStatus": 200,
      "error": null,
      "attemptCount": 1,
      "attemptedAt": "2026-07-27T20:20:49+00:00"
    }
  ]
}

status is one of success, pending_retry, or failed.

Try it

Errors

StatusMeaning
401Missing or invalid API key.
402Free developer access has expired; a subscription is required to continue.
404The search filter doesn't exist.
422Request body failed validation (missing/invalid field).
429Rate limit exceeded. 60 requests per minute per API key.

Errors follow the shape {"detail": "..."}.