Stream EstateStream Estate
Référence

Format de réponse

JSON:API par défaut, JSON « à plat » sur demande — et comment lire relationships / included

L'API renvoie chaque réponse dans l'un de deux formats, choisi par l'en-tête Accept de la requête :

Accept envoyéFormat renvoyéContent-Type de la réponse
aucun, */* ou application/*JSON:API (défaut)application/vnd.api+json
application/jsonJSON « à plat »application/json
application/vnd.api+jsonJSON:APIapplication/vnd.api+json
tout autre typeerreur 406application/problem+json

Les deux formats portent les mêmes données ; seule l'organisation change. Les erreurs, elles, sont toujours en application/problem+json (voir Codes d'erreur).

Si vous ne précisez rien, vous recevez du JSON:API. La plupart des clients HTTP (curl, fetch, Postman) envoient Accept: */* par défaut, ce qui revient au même.

JSON:API — data, relationships, included

Dans le format JSON:API, chaque objet n'est décrit qu'une seule fois dans la réponse :

  • data contient la ressource principale : son type, son id, ses champs dans attributes, et ses liens vers d'autres ressources dans relationships.
  • relationships ne contient que des références (type + id), jamais les objets complets.
  • included est le tableau, au premier niveau du document, qui contient les objets complets référencés.

Exemple sur GET /properties/{id} :

{
  "data": {
    "type": "Property",
    "id": "019fe5b2-001f-78c9-8ed3-968ddffbb712",
    "attributes": {
      "propertyType": "FLAT",
      "transaction": { "type": "SELL", "saleType": "STANDARD" },
      "area": { "displayed": 54, "unit": "SQM" },
      "unit": { "rooms": 3, "bedrooms": 2 }
    },
    "relationships": {
      "listings": {
        "data": [
          { "type": "Listing", "id": "019fe5b1-c448-7b00-a993-feacc6966090" },
          { "type": "Listing", "id": "019feed7-d188-7c96-be53-9b85ed93df85" }
        ]
      },
      "publishers": {
        "data": [
          { "type": "Publisher", "id": "019eff3c-28d9-7c10-8a44-9f2f00329980" }
        ]
      },
      "propertyPublishers": {
        "data": [
          { "type": "PropertyPublisher", "id": "5b1f7c2e-8a41-5d3b-9c6e-2f4a1b7d9e01" }
        ]
      }
    },
    "links": { "self": "/properties/019fe5b2-001f-78c9-8ed3-968ddffbb712" }
  },
  "links": { "self": "/properties/019fe5b2-001f-78c9-8ed3-968ddffbb712" },
  "included": [
    {
      "type": "Listing",
      "id": "019fe5b1-c448-7b00-a993-feacc6966090",
      "attributes": {
        "url": "https://www.example-realestate.com/listings/019fe5b1",
        "publishedAt": "2026-04-15T09:00:00+00:00",
        "lastSeenAt": "2026-05-12T08:30:00+00:00",
        "createdAt": "2026-04-15T09:05:00+00:00",
        "updatedAt": "2026-05-12T08:30:00+00:00"
      },
      "relationships": {
        "publisher": { "data": { "type": "Publisher", "id": "019eff3c-28d9-7c10-8a44-9f2f00329980" } },
        "source": { "data": { "type": "Source", "id": "019dcfc7-3f71-7d6f-a0e6-89d8e1f9844b" } }
      }
    },
    {
      "type": "Publisher",
      "id": "019eff3c-28d9-7c10-8a44-9f2f00329980",
      "attributes": {
        "publisherType": "REAL_ESTATE_AGENCY",
        "agencyName": "Foncia Paris 18",
        "website": "https://www.foncia-paris18.example.com"
      },
      "links": { "self": "/publishers/019eff3c-28d9-7c10-8a44-9f2f00329980" }
    },
    {
      "type": "PropertyPublisher",
      "id": "5b1f7c2e-8a41-5d3b-9c6e-2f4a1b7d9e01",
      "attributes": {
        "mandate": "EXCLUSIVE",
        "reference": "REF-AB42",
        "contact": { "email": "contact@foncia-paris18.example.com", "phone": "+33123456789" }
      },
      "relationships": {
        "property": { "data": { "type": "Property", "id": "019fe5b2-001f-78c9-8ed3-968ddffbb712" } },
        "publisher": { "data": { "type": "Publisher", "id": "019eff3c-28d9-7c10-8a44-9f2f00329980" } }
      }
    },
    {
      "type": "Source",
      "id": "019dcfc7-3f71-7d6f-a0e6-89d8e1f9844b",
      "attributes": { "slug": "realty-portal" },
      "links": { "self": "/sources/019dcfc7-3f71-7d6f-a0e6-89d8e1f9844b" }
    }
  ]
}

Le bloc relationships.listings de la property référence ses deux annonces ; les annonces elles-mêmes sont dans included, avec leur type: "Listing". Chaque Listing référence à son tour son Publisher (annonceur) et sa Source (portail), eux aussi présents dans included.

Mandat, référence et contact de l'annonceur

Un même annonceur peut apparaître sur plusieurs biens d'une page, mais il n'est décrit qu'une fois dans included. Ce qu'il déclare sur un bien précis — mandate (son mandat), reference (la référence interne de l'agence) et contact — ne peut donc pas vivre sur le Publisher : ces champs sont portés par une ressource PropertyPublisher, une par couple bien / annonceur.

  • La property la référence dans relationships.propertyPublishers, avec autant d'entrées que dans relationships.publishers.
  • Chaque PropertyPublisher pointe vers son bien (relationships.property) et son annonceur (relationships.publisher).
  • Un annonceur présent sur deux biens de la page donne un seul Publisher et deux PropertyPublisher, chacun avec les valeurs de son bien.
  • Son id est stable pour un même couple bien / annonceur, mais aucune route ne le sert : il n'a pas de links.self.

Les valeurs sont les mêmes que dans publishers[] du JSON à plat.

Résoudre une référence

Pour retrouver un objet référencé : prenez son type et son id dans relationships, puis cherchez l'entrée de même type et même id dans included. Le couple (type, id) est unique dans tout le document.

const doc = await res.json(); // JSON:API

const byKey = new Map(
  (doc.included ?? []).map((r) => [`${r.type}:${r.id}`, r]),
);
// Une référence vaut null quand l'objet n'est pas exposé (annonce sans annonceur ou source connus).
const resolve = (ref) => (ref ? byKey.get(`${ref.type}:${ref.id}`) : undefined);

const property = doc.data;
const listings = property.relationships.listings.data.map(resolve);
const publishers = property.relationships.publishers.data.map(resolve);

// Chaque annonce pointe vers son annonceur et son portail
for (const listing of listings) {
  const publisher = resolve(listing.relationships.publisher.data);
  const source = resolve(listing.relationships.source.data);
  console.log(listing.attributes.url, publisher?.attributes.agencyName, source?.attributes.slug);
}

Types de ressources

typeRessourceOù on la trouve
PropertyLe bien dédoublonné — vue consolidée dans attributesdata
ListingUne annonce source, avec ses valeurs propres (URL, dates, écarts par rapport au bien)included, référencée par relationships.listings
PublisherUn annonceur distinct (agence, particulier…)included, référencée par relationships.publishers et par Listing.relationships.publisher
PropertyPublisherCe qu'un annonceur déclare sur un bien : mandate, reference, contactincluded, référencée par relationships.propertyPublishers
SourceLe portail d'où provient l'annonceincluded, référencée par Listing.relationships.source

Pourquoi plusieurs Listing et plusieurs Publisher sur un même bien : voir Property — agrégation et déduplication.

Collections : data[] et meta

Sur POST /properties, data est un tableau de ressources Property, et la pagination est portée par meta :

{
  "data": [ { "type": "Property", "id": "…", "attributes": { "…": "…" }, "relationships": { "…": "…" } } ],
  "meta": {
    "totalItems": 4231,
    "hasNextPage": true,
    "cursor": "eyJzb3J0IjpbMTc3ODg0NDY0MzAwMCwiMDE5Z…",
    "pagination": { "type": "CURSOR", "size": 50, "nextRequestBody": { "…": "…" } }
  },
  "links": { "self": "/properties" },
  "included": [ /* Listing, Publisher, PropertyPublisher, Source de toutes les properties de la page */ ]
}

included est partagé par toute la page : un même Publisher publié sur plusieurs biens n'y figure qu'une fois. Voir Pagination.

JSON « à plat » — Accept: application/json

Envoyez Accept: application/json pour recevoir les objets complets imbriqués dans chaque bien, sans relationships ni included :

curl "https://api-v2.stream.estate/properties/019fe5b2-001f-78c9-8ed3-968ddffbb712" \
  -H "X-API-KEY: <votre_clé>" \
  -H "Accept: application/json"
{
  "id": "019fe5b2-001f-78c9-8ed3-968ddffbb712",
  "property": {
    "propertyType": "FLAT",
    "transaction": { "type": "SELL", "saleType": "STANDARD" },
    "area": { "displayed": 54, "unit": "SQM" },
    "unit": { "rooms": 3, "bedrooms": 2 }
  },
  "publishers": [
    {
      "id": "019eff3c-28d9-7c10-8a44-9f2f00329980",
      "publisherType": "REAL_ESTATE_AGENCY",
      "agencyName": "Foncia Paris 18",
      "website": "https://www.foncia-paris18.example.com"
    }
  ],
  "listings": [
    {
      "id": "019fe5b1-c448-7b00-a993-feacc6966090",
      "url": "https://www.example-realestate.com/listings/019fe5b1",
      "source": { "id": "019dcfc7-3f71-7d6f-a0e6-89d8e1f9844b", "slug": "realty-portal" },
      "publishedAt": "2026-04-15T09:00:00+00:00",
      "lastSeenAt": "2026-05-12T08:30:00+00:00",
      "createdAt": "2026-04-15T09:05:00+00:00",
      "updatedAt": "2026-05-12T08:30:00+00:00"
    }
  ]
}

C'est le format décrit dans Property — schéma.

Sur POST /properties, le format à plat renvoie un tableau nu de biens ([ { "id": …, "property": …, … } ]), sans totalItems, hasNextPage ni cursor. Pour paginer une recherche — en particulier en mode CURSOR, qui exige de renvoyer le curseur reçu — utilisez le format JSON:API (défaut) et lisez meta.

Correspondance entre les deux formats

JSON:APIJSON à plat
data.idid
data.attributesproperty
data.relationships.publishers.data[] → included (type: "Publisher")publishers[]
data.relationships.propertyPublishers.data[] → included (type: "PropertyPublisher")publishers[].mandate, publishers[].reference, publishers[].contact
data.relationships.listings.data[] → included (type: "Listing")listings[]
Listing.relationships.source.data → included (type: "Source")listings[].source
Listing.relationships.publisher.data → included (type: "Publisher")l'entrée correspondante de publishers[]
data[] + meta.totalItems / meta.hasNextPage / meta.cursor (recherche)tableau nu, sans métadonnées de pagination

Voir aussi

  • Property — pourquoi un bien regroupe plusieurs annonces et plusieurs annonceurs
  • Pagination — meta.totalItems, meta.hasNextPage, meta.cursor
  • Codes d'erreur — format problem+json
Version 0.14.289Dernière mise à jour

Sur cette page