Stream EstateStream Estate
Reference

Response format

JSON:API by default, flat JSON on request — and how to read relationships / included

The API returns every response in one of two formats, selected by the request's Accept header:

Accept sentFormat returnedResponse Content-Type
none, */* or application/*JSON:API (default)application/vnd.api+json
application/jsonflat JSONapplication/json
application/vnd.api+jsonJSON:APIapplication/vnd.api+json
any other type406 errorapplication/problem+json

Both formats carry the same data; only the layout differs. Errors are always application/problem+json (see Error codes).

If you send nothing, you get JSON:API. Most HTTP clients (curl, fetch, Postman) send Accept: */* by default, which amounts to the same thing.

JSON:API — data, relationships, included

In the JSON:API format, every object is described exactly once in the response:

  • data holds the primary resource: its type, its id, its fields under attributes, and its links to other resources under relationships.
  • relationships only holds references (type + id), never full objects.
  • included is the top-level array holding the full objects being referenced.

Example on 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" }
    }
  ]
}

The property's relationships.listings block references its two ads; the ads themselves live in included, with type: "Listing". Each Listing in turn references its Publisher (advertiser) and its Source (portal), also present in included.

The publisher's mandate, reference and contact

One publisher can appear on several properties of a page, but it is described once in included. What it states on one given property — mandate (its mandate), reference (the agency's own reference) and contact — therefore cannot live on the Publisher: these fields sit on a PropertyPublisher resource, one per property / publisher pair.

  • The property references it in relationships.propertyPublishers, with as many entries as relationships.publishers.
  • Each PropertyPublisher points to its property (relationships.property) and its publisher (relationships.publisher).
  • A publisher appearing on two properties of the page gives one Publisher and two PropertyPublisher, each with its own property's values.
  • Its id is stable for a given property / publisher pair, but no route serves it: it has no links.self.

The values are the same as in flat JSON publishers[].

Resolving a reference

To find a referenced object: take its type and id from relationships, then look up the entry with the same type and the same id in included. The (type, id) pair is unique across the whole document.

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

const byKey = new Map(
  (doc.included ?? []).map((r) => [`${r.type}:${r.id}`, r]),
);
// A reference is null when the object is not exposed (a listing with no known publisher or source).
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);

// Each ad points to its advertiser and its portal
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);
}

Resource types

typeResourceWhere to find it
PropertyThe deduplicated asset — consolidated view under attributesdata
ListingOne source ad, with its own values (URL, dates, deviations from the property)included, referenced by relationships.listings
PublisherOne distinct advertiser (agency, private individual…)included, referenced by relationships.publishers and by Listing.relationships.publisher
PropertyPublisherWhat one publisher states on one property: mandate, reference, contactincluded, referenced by relationships.propertyPublishers
SourceThe portal the ad was crawled fromincluded, referenced by Listing.relationships.source

Why one property carries several Listing and several Publisher entries: see Property — aggregation & deduplication.

Collections: data[] and meta

On POST /properties, data is an array of Property resources, and pagination lives under 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 of every property on the page */ ]
}

included is shared by the whole page: a Publisher advertising several properties appears only once. See Pagination.

Flat JSON — Accept: application/json

Send Accept: application/json to receive full objects nested inside each property, with no relationships and no included:

curl "https://api-v2.stream.estate/properties/019fe5b2-001f-78c9-8ed3-968ddffbb712" \
  -H "X-API-KEY: <your_key>" \
  -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"
    }
  ]
}

This is the format described in Property — schema.

On POST /properties, the flat format returns a bare array of properties ([ { "id": …, "property": …, … } ]), with no totalItems, hasNextPage or cursor. To paginate a search — especially in CURSOR mode, which requires echoing the cursor you received — use the JSON:API format (default) and read meta.

Mapping between the two formats

JSON:APIFlat JSON
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")the matching entry of publishers[]
data[] + meta.totalItems / meta.hasNextPage / meta.cursor (search)bare array, no pagination metadata

See also

  • Property — why one asset groups several ads and several advertisers
  • Pagination — meta.totalItems, meta.hasNextPage, meta.cursor
  • Error codes — problem+json format
Version 0.14.289Last updated

On this page