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.
- Create a developer account. The same account works for the WoningMe app.
- Open your dashboard and click "Generate API key". Copy it immediately, it's shown once.
- Create a search filter (below) and start querying matching rental properties, or add a
webhook_urlto get notified automatically.
A machine-readable OpenAPI spec is also available, for client generators, Postman/Insomnia import, and similar tooling.
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.
POST /api/developer/v1/searches
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.
| Field | Type | Required | Notes |
|---|---|---|---|
label | string | yes | A name you choose for this search filter. |
city | string | yes | Dutch city or municipality name. |
radius_km | number | yes | Search radius in km from the city center. |
is_free_market | boolean | no | Include private-sector (vrije sector) listings. Segment flags are independent; see note above. |
is_social_housing | boolean | no | Include housing-association / public-sector (corporatie, sociale huur) listings. |
is_middenhuur | boolean | no | Include mid-market rent (middenhuur) listings. |
min_price | integer | no | Minimum monthly rent in whole euros. Omit for no minimum. |
max_price | integer | no | Maximum monthly rent in whole euros. Omit for no maximum. |
min_rooms | integer | no | Minimum number of rooms. Omit for no minimum. |
min_surface | integer | no | Minimum floor area in square meters. Omit for no minimum. |
webhook_url | string | no | HTTPS 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, ... }
]
}
GET /api/developer/v1/searches/{id}
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"
PATCH /api/developer/v1/searches/{id}
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}'
DELETE /api/developer/v1/searches/{id}
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 param | Default | Notes |
|---|---|---|
source | none | filter to one source, e.g. nederwoon |
property_type | none | filter to one type, e.g. apartment, house, studio, room |
min_price | none | whole euros; combines with the search filter's own min_price, the stricter of the two wins |
max_price | none | whole euros; combines with the search filter's own max_price, the stricter of the two wins |
min_rooms | none | combines with the search filter's own min_rooms, the stricter of the two wins |
min_surface | none | mΒ²; combines with the search filter's own min_surface, the stricter of the two wins |
sort | price | price, surface, rooms, available, city, last_synced_at, first_seen_at |
order | asc | asc or desc |
skip | 0 | for pagination |
limit | 200 | max 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.
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.
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.
Errors
| Status | Meaning |
|---|---|
401 | Missing or invalid API key. |
402 | Free developer access has expired; a subscription is required to continue. |
404 | The search filter doesn't exist. |
422 | Request body failed validation (missing/invalid field). |
429 | Rate limit exceeded. 60 requests per minute per API key. |
Errors follow the shape {"detail": "..."}.