Migration v1 → v2
Migration guide from Stream Estate API v1 to v2
Breaking changes between v1 (docs.stream.estate) and v2 (https://api-v2.stream.estate).
This migration changes most URLs, the search format (querystring → structured body built around a criteria tree), renames Search to Alert, and introduces a real destination system (EventDestination). Plan for a client rewrite.
TL;DR
- Base URL —
api-v2.stream.estate - Auth — same
X-API-KEYheader, but a new key must be generated (v1 keys do not work on v2) - Search —
GET /documents/properties?...→POST /propertieswith a JSON body built around a recursivecriteriatree (Filters/and/or), paginationPAGEorCURSOR - Renaming —
Search→Alert(+ dedicatedAlertEvents) - Webhooks — full system via
EventDestination+signingKey+EventNotification(delivery log) - Indicators —
/citiesreplaced by/geo/administrative-divisions/{uuid}(UUID lookup only, no collection endpoint); a new set of estimation endpoints (price per m², POI, comparables) is on the roadmap to replace the old/indicators/*
Endpoint mapping
Properties
| v1 | v2 | Note |
|---|---|---|
GET /documents/properties (70+ query params) | POST /properties (JSON body, criteria tree) | See Filtering |
GET /documents/properties/{id} | GET /properties/{uuid} | Shorter path |
GET /documents/properties/{id}/similar-properties | 🆕 New estimation endpoints coming |
Searches → Alerts
| v1 | v2 | Note |
|---|---|---|
GET /searches | GET /alerts | |
GET /searches/{id} | GET /alerts/{uuid} | |
POST /searches | POST /alerts | Body reworked around criteria + notificationDestination |
PUT /searches/{id} | PUT /alerts/{uuid} | |
DELETE /searches/{id} | DELETE /alerts/{uuid} | |
| (N/A) | GET /alerts/{alertUuid}/events | Match history |
| (N/A) | GET /alerts/{alertUuid}/events/{uuid} | Event detail |
Webhooks & notifications
| v1 | v2 | Note |
|---|---|---|
| Webhook URL set on the Search | EventDestination (GET/POST on the collection, GET/DELETE by UUID) + HMAC signingKey | Reusable destinations across alerts (no update — recreate the destination instead) |
GET /webhook-tester | → integrated into EventDestination + EventNotification | No dedicated endpoint needed |
Events ad.update.*, property.ad.* | New types: NEW_MATCH, PRICE_CHANGE, OTHER_CHANGE, EXPIRED, REACTIVATED | See Alert |
| (N/A) | GET /event-notifications | Full delivery-attempt log |
| (N/A) | Self-recovering circuit breaker | A suspended destination reactivates on its own once the endpoint answers; can be forced from the console (see Notifications) |
Cities / Geo
| v1 | v2 | Note |
|---|---|---|
GET /cities | GET /geo/administrative-divisions/{uuid} (UUID lookup) | No collection endpoint for now |
GET /public/location-autocomplete | GET /geo/autocomplete |
Indicators & misc
| v1 | v2 | Note |
|---|---|---|
GET /indicators/points_of_interest | 🆕 New estimation endpoints coming | |
GET /indicators/price_per_meter | 🆕 New estimation endpoints coming |
New in v2
| Endpoint | Description |
|---|---|
GET /account/api-usage | Billable usage aggregates (totals, byApiKey, byRoute) over a startDate/endDate window |
GET /analytics/listings | Aggregated listing analytics |
GET /favorite-properties, POST /favorite-properties, DELETE /favorite-properties/{uuid} | Favorites (list + create on the collection, delete by UUID only) |
GET /event-notifications[/{uuid}] | Notification delivery log |
Model changes
Property
In v1, PropertyDocument contains an inlined adverts[] with all details. In v2, POST /properties returns a structured PropertySearchResultDTO: a canonical view under property, deduplicated publishers, and per-listing variants:
{
"id": "019dedb1-fdf4-714c-8392-5b9cfd63cde3",
"property": {
"createdAt": "2025-01-15T10:30:00+00:00",
"updatedAt": "2025-06-15T12:00:00+00:00",
"propertyType": "FLAT",
"transaction": { "type": "SELL" /* + status, saleType, availableAt */ },
"area": { /* displayed, indoor, outdoor, land */ },
"unit": { /* rooms, bedrooms, floor, condition */ },
"building": { /* constructionYear, floors, condition */ }
},
"publishers": [/* deduplicated PublisherSectionDTO[] */],
"listings": [/* ListingSectionDTO[]: url, source, publishedAt, lastSeenAt, and any variants that diverge from the canonical view */]
}Canonical details (area, type, rooms, transaction, etc.) live under property. listings[] only carries variants specific to each ad (title, URL, publication dates, and any field that diverges from the canonical view).
Alert (formerly Search)
Body reworked — a single criteria tree (no searchMode discriminator anymore) and a declarative destination via notificationDestination:
{
"name": "3+ room flats in Paris",
"active": true,
"criteria": {
"property": {
"type": { "in": ["FLAT"] },
"transaction": { "type": "SELL" },
"unit": { "rooms": { "gte": 3 } },
"locations": { "countryCode": "FR", "in": { "uniqueCodes": ["75056"] } }
}
},
"notificationConfig": {
"rules": [
{ "eventType": "NEW_MATCH" },
{ "eventType": "PRICE_CHANGE", "variationPercent": [{ "lte": -5 }] }
]
},
"notificationDestination": {
"webhookUrl": "https://your.app/hook"
}
}The POST /alerts response returns a signingKey HMAC once (prefixed whsec_) — store it securely.
Text search (formerly expressions)
In v1, expressions[i][j] carried a word and two options, strict and includes. In v2, each text field takes an object { "in": [...], "nin": [...] } under criteria.property.body, where every entry is a phrase (words side by side, in order; case and accents ignored):
| v1 | v2 |
|---|---|
word: "à rénover", strict: true (exact phrase) | "description": { "in": ["à rénover"] } |
word: "à rénover", strict: false (independent words) | "description": { "in": ["à", "rénover"] } — a single word is a one-word phrase |
includes: false (exclude) | "description": { "nin": ["bail commercial"] } |
expressions[0] OR expressions[1] | an or node with one leaf per expression |
expressions[0][0] AND expressions[0][1] | an and node with one leaf per word or phrase |
v2 targets one field (title or description) where v1 searched both: to keep that behaviour, put both fields inside an or node. See Filtering — Text search.
Response format
| v1 | v2 |
|---|---|
| Classic collection | JSON:API (application/vnd.api+json) by default — data[] + meta + included — or flat JSON via Accept: application/json (see Response format) |
Query pagination ?page=N | POST /properties with paginationType: "PAGE" or "CURSOR" (see Pagination) |
Migration checklist
- 🔑 Generate a new v2 API key at console.stream.estate/api-keys (v1 keys are incompatible) and send it in
X-API-KEY - 🔄 Replace
GET /documents/properties?...withPOST /propertiesand a JSON body built around acriteriatree (Filters/and/or) - 🔄 Adapt pagination (choose
PAGEorCURSORviapaginationType) - 🔄 Rename
Search*→Alert*in client code - 🔄 Create explicit
EventDestinations (POST /account/event-destinations, thenGET/DELETEby UUID — no update, recreate the destination instead) rather than passing a webhook URL on each Search, or usenotificationDestination.webhookUrlwhen creating the Alert - 🔐 Store the
signingKeyreturned byPOST(alert or event-destination) and verify the HMAC signature on receipt - 🔄 Migrate consumers of
PropertyDocument.adverts[]toPropertySearchResult.listings[](top-level in the response, alongsidepropertyandpublishers) - 🔄 Replace v1 event names with v2 enums (
NEW_MATCH,PRICE_CHANGE,OTHER_CHANGE,EXPIRED,REACTIVATED) - ⚠️ Temporarily remove calls to
/indicators/*— a new estimation-focused suite of endpoints is on the way - ✨ Optionally integrate
/event-notifications(delivery log) and/analytics/listings - 🔄 Convert each
expressions[i][j]intoproperty.body.<field>.in/nin—strict: truebecomes a phrase,strict: falseone word per entry,includes: falsemoves tonin(see Text search)