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 sent | Format returned | Response Content-Type |
|---|---|---|
none, */* or application/* | JSON:API (default) | application/vnd.api+json |
application/json | flat JSON | application/json |
application/vnd.api+json | JSON:API | application/vnd.api+json |
| any other type | 406 error | application/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:
dataholds the primary resource: itstype, itsid, its fields underattributes, and its links to other resources underrelationships.relationshipsonly holds references (type+id), never full objects.includedis 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 asrelationships.publishers. - Each
PropertyPublisherpoints to its property (relationships.property) and its publisher (relationships.publisher). - A publisher appearing on two properties of the page gives one
Publisherand twoPropertyPublisher, each with its own property's values. - Its
idis stable for a given property / publisher pair, but no route serves it: it has nolinks.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
type | Resource | Where to find it |
|---|---|---|
Property | The deduplicated asset — consolidated view under attributes | data |
Listing | One source ad, with its own values (URL, dates, deviations from the property) | included, referenced by relationships.listings |
Publisher | One distinct advertiser (agency, private individual…) | included, referenced by relationships.publishers and by Listing.relationships.publisher |
PropertyPublisher | What one publisher states on one property: mandate, reference, contact | included, referenced by relationships.propertyPublishers |
Source | The portal the ad was crawled from | included, 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:API | Flat JSON |
|---|---|
data.id | id |
data.attributes | property |
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+jsonformat