Rendering property details
Source-reviewed draft; deployment and release verification pending.
Draft — source-reviewed on 22 October 2026. Deployment and live publication state have not been verified. Examples contain invented data only.
Load and preserve the operation
Load GET /api/v1/public/properties/P9001?operation=SALES after a visitor selects the synthetic listing P9001. Preserve the card's SALES or RENTAL context. Without a query operation, detail prefers SALES when supported; an unsupported requested context returns 422. Do not silently switch the selected operation after an error.
Detail requires public_id, operation, operations, prices, view_types, catalogs, characteristics and gallery. Arrays may be empty and characteristics may be {}. operations supplies the supported contexts; prices contains valid available prices with their operation. Selected price is optional. Availability here is public eligibility, not date-based booking availability.
Compose the page from public fields
| Page component | Data | Missing/null behavior |
|---|---|---|
| Heading | name, public_id | Use an intentional heading fallback when name is unknown. |
| Pricing | price, prices, operations, optional asking_price_per_m2 | Keep amount strings and show kind/period; omit unknown price metrics. |
| Narrative | description, description_translations | Show an approved empty state; select an available language without assuming translations exist. |
| Facts | Classification, counts, total_area_m2, view_types, characteristics, location_facts | Render known facts; distinguish false/zero from unknown. |
| Location | city, region, ubicacion, location, address_line | Hide map if location is absent/null; display only the permitted address line. |
| Gallery | gallery | Empty is valid; use a neutral placeholder. Detail has no thumbnail field. |
| Contact | agent | Hide whole card if absent/null; hide unavailable individual contact methods. |
Optional nullable fields are normally omitted by the current projection. Accept both omission and allowed nulls. area, complex_name, rental_type, updated_at, publication status and an availability field are not detail response fields. Do not recreate them from private sources.
Render descriptions as text unless a separate, reviewed sanitization policy authorizes markup. The API provides strings, not a guarantee of trusted HTML. Render labels using normal React text interpolation.
Complete sale property response
The same full example appears in API Reference → Properties → Get property detail, under the 200 response example completeSale. This is a literal public API response fixture, not a proposed UI model. It populates all 26 currently supported top-level detail fields, all 35 characteristics, all seven location facts and all seven public agent fields.
Request: GET /api/v1/public/properties/P9001?operation=SALES on the approved API origin (still to be confirmed; the documentation domain is not the API origin).
All values below are invented. This fixture represents a listing whose FULL address visibility permits its fictional exact public point and fictional address. For STREET visibility, omit address_line and use only the returned APPROXIMATE grid point. For HIDDEN detail, omit location and address_line. No visibility flag is returned. Optional data is normally omitted when unknown, so do not require all these example fields in every response. false and 0 remain meaningful values.
Catalog and LocalArea keys, climate labels/colors in reference examples, contact details and media asset IDs are synthetic. Retrieve current reference records before sending registry-backed filters; do not hard-code these fixture keys. catalogs below is an array of membership keys; labels and hierarchy live in reference data. Public image URLs identify sanitized derivatives only; these synthetic assets do not download real images. caption: "TERRACE" is the current image label string, not free-form alt text; the response schema exposes it as an optional string.
{
"public_id": "P9001",
"name": "Example coastal penthouse duplex",
"operation": "SALES",
"operations": [
"SALES"
],
"prices": [
{
"amount": "425000.00",
"currency": "EUR",
"kind": "SALE",
"period": "ONE_OFF",
"operation": "SALES"
}
],
"price": {
"amount": "425000.00",
"currency": "EUR",
"kind": "SALE",
"period": "ONE_OFF"
},
"asking_price_per_m2": "4250.00",
"property_type": "HOMES",
"features_type": "FLAT",
"property_detail_type": "PENTHOUSE_DUPLEX",
"bedrooms": 3,
"bathrooms": 2,
"total_area_m2": 100,
"city": "Adeje",
"region": "Santa Cruz de Tenerife",
"ubicacion": {
"key": "example-local-area",
"label": "Example local area",
"city": "Adeje"
},
"view_types": [
"SEA",
"MOUNTAIN"
],
"catalogs": [
"example-sale-collection"
],
"location_facts": {
"altitude_m": 120,
"climate_zone": "TENERIFE_SOUTH",
"distance_to_sea_m": 900,
"distance_to_airport_km": 20,
"distance_to_nearest_school_km": 1.2,
"distance_to_nearest_healthcare_km": 2.4,
"short_term_rental_possible": false
},
"location": {
"lat": 28.1,
"lng": -16.7,
"precision": "EXACT"
},
"description": "Invented sale listing for frontend layout and API mocks.",
"description_translations": {
"en": "Invented sale listing for frontend layout and API mocks.",
"es": "Anuncio ficticio para ejemplos de interfaz."
},
"characteristics": {
"floor": "3",
"toilets": 1,
"living_area_m2": 85,
"outdoor_area_m2": 15,
"terrace_area_m2": 15,
"pool_area_m2": 40,
"garden_area_m2": 60,
"built_year": 2010,
"features_conservation": "GOOD",
"pool_type": "PUBLIC_HEATED",
"parking_type": "PARKING_SPACE",
"parking_spaces": 1,
"garden_type": "COMMUNAL",
"kitchen_type": "AMERICAN",
"furnishing_status": "PARTIALLY_FURNISHED",
"terrace_glazing": "NOT_GLAZED",
"window_features": [
"ALUMINUM",
"BLINDS"
],
"is_renovated": true,
"last_renovated_year": 2024,
"is_new_construction": false,
"has_terrace": true,
"has_garden": true,
"has_air_conditioning": true,
"has_storage": true,
"has_lift": true,
"has_wardrobes": true,
"has_terrace_awning": true,
"has_jacuzzi": false,
"has_security_door": true,
"has_double_glazing": true,
"has_alarm": false,
"has_fireplace": false,
"has_laundry_room": true,
"has_solarium": true,
"has_home_cinema": false
},
"gallery": [
{
"asset_id": "00000000-0000-4000-8000-000000009001",
"url": "/api/v1/public/properties/P9001/images/00000000-0000-4000-8000-000000009001",
"caption": "TERRACE",
"is_primary": true,
"display_order": 0
}
],
"agent": {
"display_name": "Example Agent",
"photo_url": "/api/v1/public/agent-images/00000000-0000-4000-8000-000000009002",
"public_phone": "+12025550101",
"public_email": "[email protected]",
"whatsapp_phone": "+12025550101",
"preferred_contact_method": "EMAIL",
"preferred_contact_time": "Weekdays, 10:00–16:00 Atlantic/Canary"
},
"address_line": "Example Street 1 (fictional address)"
}A sale uses operation: "SALES" and price kind: "SALE", period: "ONE_OFF". Monetary values are strings. short_term_rental_possible: false is a published marketing fact; it is not booking availability. features_type: "FLAT" with property_detail_type: "PENTHOUSE_DUPLEX" is one canonical classification. Sale listings do not gain rental fields merely because the schema supports rental contexts.
Missing optional fields
The API Reference also includes sparseSale, a complete response with only required detail fields. It intentionally has no price, location, description, media or agent. A public profile card must not fall back to private CRM profile data.
{
"public_id": "P9002",
"operation": "SALES",
"operations": [
"SALES"
],
"prices": [],
"view_types": [],
"catalogs": [],
"characteristics": {},
"gallery": []
}Public agent contact display
Only display the returned public profile projection. It contains required display_name when present, plus optional nullable photo_url, public_phone, public_email, whatsapp_phone, preferred_contact_method and preferred_contact_time. There is no fallback to an account email, private phone, login identity or private profile image.
A missing assigned agent, inactive user, disabled public profile or missing public display name can remove the entire card. The current profile preference values are PHONE, EMAIL and WHATSAPP, but the public schema exposes strings; handle an unknown preference without breaking rendering. A preference does not guarantee its channel is available. Construct and validate mailto:, tel: and WhatsApp actions safely; never interpolate contact values as HTML.
// Presentation behavior only; `detail` uses the reviewed public response type.
const agent = detail.agent;
const emailForDisplay = agent?.public_email ?? undefined;
const showContactCard = agent != null;
// Keep channel visibility separate from preferred_contact_method.An agent image remains independently public while its active owner's public profile and photo eligibility remain enabled. Withdrawing this property alone does not revoke that image endpoint.
States and privacy
Show page loading and a bounded retry state without exposing internal error details. Treat 404 as unavailable, whether caused by a missing listing, withdrawal or a disabled capability; do not probe private routes. Treat an operation-related 422 as a selection problem. Respect 429 retry guidance and handle 503 as temporary failure.
Use location.precision to label approximate maps. Never reverse-geocode an approximate point to present an exact property address. Only render address_line if supplied. Keep private investment values and CRM-only records outside page data. Follow no-store; refresh current detail before relying on previously loaded information.
Related operations
Detail: GET /api/v1/public/properties/{public_id}; discovery: POST /api/v1/public/properties/search; controlled metadata: GET /api/v1/public/property-reference; gallery bytes: GET /api/v1/public/properties/{public_id}/images/{asset_id}; agent photo: GET /api/v1/public/agent-images/{asset_id}. See Loading public media and Publication and cache behavior.
Updated 1 day ago