Filtrage
Composer un arbre criteria pour POST /properties
Le body de POST /properties repose sur un seul champ criteria — un arbre récursif dont chaque nœud prend exactement l'une de ces trois formes :
- une feuille
Filters(un regroupement plat de filtres par facette — le cas le plus courant) ; - un nœud AND
{ "and": [/* enfants */] }— tous les enfants doivent correspondre ; - un nœud OR
{ "or": [/* enfants */] }— au moins un enfant doit correspondre.
Omettez criteria pour faire correspondre toutes les properties.
Forme du body
{
"criteria": { /* CriteriaNode */ },
"paginationType": "PAGE",
"page": 1,
"size": 20
}| Champ | Type | Valeur |
|---|---|---|
criteria | CriteriaNode | null | Feuille Filters, nœud and, ou nœud or. |
paginationType | enum | "PAGE" (défaut) ou "CURSOR". Voir Pagination. |
page | integer | Honoré uniquement en PAGE. Défaut 1, minimum 1. |
cursor | string | null | Honoré uniquement en CURSOR. Renvoyez la valeur reçue dans la réponse précédente. |
size | integer | 0–100. Défaut 10. Avec 0, la réponse ne contient aucun bien, seulement leur nombre : voir Compter sans récupérer les biens. |
sort | object | Directive de tri (champ + direction). |
La feuille Filters
Une feuille regroupe les filtres par facette (toutes combinées en AND au sein de la feuille) :
| Facette | DTO | Cible |
|---|---|---|
property | PropertyFacetFiltersDTO | type, transaction, pricing, areas, locations, unit, building, amenities, body. |
publisher | PublisherFiltersDTO | type, mandate, references, hasEmail, hasPhone (toutes les clauses portent sur le même publisher). |
listings | ListingsFiltersDTO | url, sources, marketing, expiredAt, priceCoherent (toutes les clauses portent sur le même listing, sauf priceCoherent, jugé au niveau de la property). |
dates | DatesFiltersDTO | createdAt, updatedAt. |
Pour la liste exhaustive des sous-champs de chaque facette (par exemple property.pricing.displayed, property.areas.displayed, property.unit.rooms, property.locations.in, etc.), voir /api-reference — la référence est régénérée à chaque build depuis la spec live.
Masques par défaut
Deux populations sont exclues des résultats tant que le filtre correspondant n'est pas posé :
- Annonces expirées — masquées par défaut. Poser
listings.expiredAtles inclut (restreintes à la plage demandée le cas échéant). - Prix incohérents — les properties dont toutes les annonces ont un prix jugé incohérent avec le marché local sont masquées par défaut :
listings.priceCoherentvauttrueimplicitement. Envoyezfalsepour ne retourner que ces properties. Les biens au verdict indéterminé restent visibles dans tous les cas.
Exemple — feuille simple
Appartements à vendre à Paris entre 200k€ et 600k€, 3 à 5 pièces, avec balcon :
curl -X POST "https://api-v2.stream.estate/properties" \
-H "X-API-KEY: <votre_clé>" \
-H "Content-Type: application/json" \
-d '{
"criteria": {
"property": {
"type": { "in": ["FLAT"] },
"transaction": { "type": "SELL" },
"pricing": { "displayed": { "gte": 200000, "lte": 600000 } },
"areas": { "displayed": { "gte": 50 } },
"unit": {
"rooms": { "gte": 3, "lte": 5 },
"features": { "in": ["BALCONY"] }
},
"locations": {
"countryCode": "FR",
"in": { "uniqueCodes": ["75056"] }
}
}
},
"paginationType": "PAGE",
"page": 1,
"size": 20
}'Recherche texte
property.body.title et property.body.description filtrent sur le texte des annonces. Chacun prend un objet à deux listes :
{
"criteria": {
"property": {
"body": {
"description": {
"in": ["à rénover", "travaux"],
"nin": ["bail commercial", "ehpad"]
}
}
}
},
"size": 20
}- Chaque entrée est une phrase : ses mots doivent apparaître côte à côte, dans cet ordre.
"division possible"ne retient pas une annonce où les deux mots sont à trois paragraphes l'un de l'autre. Un mot seul est une phrase d'un mot. - Casse et accents sont ignorés :
"A RENOVER"et"à rénover"sont la même requête. in— le texte doit contenir au moins une des phrases (un OU). Pour exiger deux phrases à la fois, mettez une feuille par phrase dans un nœudand.nin— le texte ne doit contenir aucune des phrases.- Jusqu'à 25 phrases par liste. Les deux listes sont facultatives.
Une chaîne nue ("description": "travaux") est refusée avec un 400. Pour retrouver le résultat que renvoyait "division possible" — l'un ou l'autre des mots — envoyez { "in": ["division", "possible"] } ; pour la phrase, { "in": ["division possible"] }.
Norme du diagnostic énergétique
property.unit.diagnostic.standard filtre sur la norme réglementaire du DPE : FR_DPE_2006 pour un DPE établi avant le 1ᵉʳ juillet 2021, FR_DPE_2021 à partir de cette date. Même triplet de clauses que unit.condition :
| Clause | Effet |
|---|---|
in | Strict : la norme doit figurer dans la liste. Les biens dont la norme est inconnue sont exclus. |
nin | La norme ne doit pas figurer dans la liste. Les biens dont la norme est inconnue passent. |
inOrMissing | La norme figure dans la liste ou est inconnue : conserve les biens dont la source n'a pas indiqué la norme. |
Une valeur hors liste est refusée avec un 400 qui nomme les valeurs acceptées. Le critère s'écrit tel quel dans le criteria d'une alerte.
Biens classés A à C dont le DPE suit la norme 2021, ou dont la norme n'est pas indiquée :
{
"criteria": {
"property": {
"unit": {
"diagnostic": {
"standard": { "inOrMissing": ["FR_DPE_2021"] },
"scores": [ { "type": "ENERGY_CONSUMPTION", "rating": ["A", "B", "C"] } ]
}
}
}
},
"size": 20
}Nœuds AND / OR
Pour combiner plusieurs feuilles, utilisez and: ou or: — chaque enfant est lui-même un CriteriaNode (donc une feuille, un autre and, ou un autre or). Chaque tableau contient entre 2 et 10 nœuds enfants, et la profondeur d'imbrication n'est pas limitée : pour combiner plus de dix branches, imbriquez un nœud supplémentaire.
{
"criteria": {
"or": [
{
"property": {
"type": { "in": ["FLAT"] },
"areas": { "displayed": { "gte": 60 } }
}
},
{
"property": {
"type": { "in": ["HOUSE"] },
"areas": { "displayed": { "gte": 100 }, "land": { "gte": 300 } }
}
}
]
},
"paginationType": "PAGE",
"size": 20
}Combien d'enfants par nœud
Un tableau and ou or doit contenir au moins 2 et au plus 10 enfants. La règle est la même pour les deux opérateurs, et elle s'applique à chaque nœud indépendamment. En dehors de ces bornes, la requête est refusée avec un 422 :
| Enfants dans le tableau | Résultat |
|---|---|
| 0 ou 1 | 422 — or must contain at least 2 children (ou and must contain at least 2 children) |
| 2 à 10 | accepté |
| 11 ou plus | 422 — or must contain at most 10 children (ou and must contain at most 10 children) |
Un nœud à un seul enfant n'a de toute façon pas d'effet : remplacez-le par son enfant. Pour combiner plus de dix branches, imbriquez — la profondeur, elle, n'est pas bornée :
{
"criteria": {
"or": [
{ "or": [ /* 10 branches */ ] },
{ "or": [ /* 10 branches de plus */ ] }
]
}
}Réponse
Au format JSON:API (défaut), les biens sont dans data[], la pagination dans meta, et les annonces / annonceurs complets dans included :
{
"data": [ /* ressources Property (type, id, attributes, relationships) */ ],
"meta": {
"totalItems": 1847,
"hasNextPage": true,
"cursor": "eyJzb3J0Ijpb..."
},
"links": { "self": "/properties" },
"included": [ /* Listing, Publisher, PropertyPublisher, Source */ ]
}meta.totalItems— nombre total de matches. En modePAGE, il est plafonné à 10 000.meta.hasNextPage—trues'il reste des résultats.meta.cursor— opaque, à renvoyer tel quel dans la requête suivante en modeCURSOR.nullen modePAGE.
Chaque résultat de recherche contient déjà l'intégralité du bien : exactement le même contenu que GET /properties/{id} (property, publishers[], listings[]). Inutile d'appeler GET /properties/{id} pour compléter un résultat ; cet appel sert uniquement à récupérer un bien dont vous connaissez déjà l'identifiant.