MyImmobilier.ch

For agencies and CRM vendors

API & CRM integration

A single JSON API to manage your listings from your CRM (ImmoBridge, Immomig, Rexx, Flowfact…), receive visitor enquiries in real time and retrieve the statistics of each property. Properties follow the SwissRETS JSON standard.

1. Authentication

From Agency area → Integrations, create a key (CRM tab). It is displayed only once; we only store its SHA-256 fingerprint. A key only gives access to the data of its own agency and can be revoked at any time.

curl https://myimmobilier.ch/api/v1/me \
  -H "Authorization: Bearer mi_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Key permissions: listings:read, listings:write, leads:read, leads:write, stats:read.

2. Sending and managing properties

A property is a SwissRETS property object. Its id is the identifier from your CRM: it makes the submission idempotent (same id = update, never a duplicate). Add the X-CRM: immobridge header to identify the source. Photos (attachments.images[].url, https) are downloaded and hosted by us (30 max, 8 MB each).

GET/api/v1/listingsList (status, updatedSince, limit, cursor).
PUT/api/v1/listings/{externalId}Creates or updates a SwissRETS property (upsert).
POST/api/v1/listingsSame, the id comes from the body (property.id).
GET/api/v1/listings/{ref}Detail. {ref} = CRM id, MY-xxxxx reference or uuid.
POST/api/v1/listings/{ref}/publishPublishes (60 days). The agency must be approved.
POST/api/v1/listings/{ref}/unpublishReverts to draft.
POST/api/v1/listings/{ref}/renewRenews for 60 days.
DELETE/api/v1/listings/{ref}Archives (removes from the portal, keeps the history).
POST/api/v1/importsBulk import: { crm, properties: [≤ 50] }, result per property (207 on partial failures).

availability.state drives the status: active/reserved → published, private → draft, reference/taken → archived. This mapping lets you simply "publish from the CRM" by resending the property.

Additional documents (rules, file…): attachments.documents[] can contain several entries, all displayed on the listing page — not just a single brochure. 3D plan / immersive tour (Matterport, iGuide…): attachments.virtualTourLinks[], like a regular virtual tour.

Hidden address: some agencies do not publish the exact address (occupied property, discreet owner…). Add the X-Hide-Address: true header on PUT/POST /listings (or {"hideAddress": true} in the body of /imports): the street, number and exact coordinates are then never published — only the town and an approximate area (300 to 450 m, whose centre is deliberately not the real address) are shown on the map, street view and cadastral map. Omit the header to leave this setting unchanged during an update; send X-Hide-Address: false to turn it off.

Displayed blocks: by default, a published listing shows the map, street view, solar simulation and cadastral map whenever the data allows it (coordinates, property type). To hide some of them without touching the address, add the X-Hide-Features header with a comma-separated list among map, streetview, solar, cadastre — for example X-Hide-Features: streetview,solar. As with the address, omitting the header leaves the existing setting unchanged; sending it with an empty list (X-Hide-Features:) shows everything again. The response returns the current state in hiddenFeatures (array) and hiddenAddress (boolean).

curl -X PUT https://myimmobilier.ch/api/v1/listings/CRM-4711 \
  -H "Authorization: Bearer mi_live_…" -H "X-CRM: immobridge" \
  -H "Content-Type: application/json" -d '{
  "id": "CRM-4711", "referenceId": "R-4711", "type": "buy",
  "categories": ["apartment"],
  "availability": { "state": "active", "start": "2026-11-01" },
  "address": { "countryCode": "CH", "locality": "Lausanne", "region": "VD", "postalCode": "1003", "street": "Rue du Lac", "streetNumber": "4" },
  "characteristics": { "numberOfRooms": 3.5, "areaBwf": 82 },
  "prices": { "currency": "CHF", "buy": { "price": 895000 } },
  "localizations": [{ "languageCode": "fr", "title": "Appartement 3.5 pièces", "description": "…",
    "attachments": { "images": [{ "url": "https://…/1.jpg" }], "documents": [{ "url": "https://…/brochure.pdf" }] } }]
}'

3. Displaying your properties on your website (showcase)

Don't want to code the display yourself? A turnkey <iframe> widget, customisable (colour, map, filters) and without an API key, can be generated from Integrations → Website. For a custom-built website that handles the display itself, use this route instead: it returns published properties only, in display format (photos, coordinates already unmasked where needed, media, badges) — unlike /listings above, which is geared towards CRM synchronisation.

GET/api/v1/vitrinePublished properties (scope listings:read, already sufficient — no dedicated key).

Same filters as the portal search: types (repeatable), prixMin, prixMax, piecesMin, surfaceMin, q (free text), sort (pertinence, prix_asc, prix_desc, surface_desc, recent), page. Paginated response { data, total, totalPages, page }, cached for 60 s (Cache-Control: public, max-age=60), unlike the other /api/v1 routes.

curl "https://myimmobilier.ch/api/v1/vitrine?types=appartement&prixMax=900000&sort=recent" \
  -H "Authorization: Bearer mi_live_…"

{
  "data": [{
    "id": "…", "reference": "MY-01012", "slug": "lausanne-appartement-…",
    "url": "https://myimmobilier.ch/bien/lausanne-appartement-…",
    "type": "appartement", "offre": "vente", "titre": "Appartement 3.5 pièces",
    "prix": 895000, "pieces": 3.5, "surface": 82, "npa": "1003", "ville": "Lausanne",
    "latitude": 46.52, "longitude": 6.63,
    "photoPrincipale": "https://…/1.jpg", "photos": ["https://…/1.jpg"],
    "medias": { "visiteVirtuelle": null, "video": null, "plan": null, "brochure": null },
    "badges": ["nouveau"], "agenceNom": "Mon Agence", "publishedAt": "2026-09-20T08:00:00Z"
  }],
  "total": 12, "totalPages": 1, "page": 1
}

4. Receiving enquiries

Contact, brochure, viewing or call-back request: each enquiry is available through the lead.created webhook (immediate) and by polling GET /api/v1/leads?since=…&unprocessed=1. Acknowledge receipt with PATCH /api/v1/leads/{id} {"acknowledged": true, "status": "traitee"} so it is not processed again.

{
  "id": "evt_…", "type": "lead.created", "created": "2026-09-26T10:12:00Z",
  "data": {
    "id": "…", "type": "brochure", "status": "nouvelle",
    "contact": { "firstName": "Anna", "lastName": "Muller", "email": "anna@…", "phone": "+41 79 …" },
    "message": "…",
    "source": "site", "stage": "nouveau",
    "budget": { "amount": 850000, "currency": "CHF", "verified": false },
    "visit": { "startsAt": "2026-10-12T14:00:00Z", "endsAt": "2026-10-12T14:30:00Z" },
    "broker": { "id": "…", "name": "Marie Dupont", "email": "marie@…", "title": "Courtière" },
    "listing": { "id": "…", "reference": "MY-01012", "externalId": "CRM-4711", "title": "…", "city": "Lausanne", "price": 895000, "offer": "vente", "type": "appartement", "url": "https://myimmobilier.ch/bien/…" }
  }
}

source: site (myimmobilier.ch form) or widget (form of the iframe embedded on your website). budget is the estimated budget attached by the visitor: calculated by them, never verified by a bank. visit only exists for a viewing booking; broker is the broker responsible for the property, if there is one. stage: nouveau, contacte, visite, offre, conclu, perdu.

listing.* events carry the property as returned by GET /listings/{ref}: status (brouillon, publie, expire, archive), exclusive, featured ("Selection"), boostedUntil (paid promotion), contactDisplay (agence, courtier, les_deux), broker, outcome ({ "type": "vendu" | "loue", "at": … }), as well as price, areas, address (hidden if requested), publication and expiry dates.

5. Statistics

Anonymous counters aggregated per day and per property: views, contacts, phoneReveals, brochureDownloads, virtualTours, videoPlays, favorites, shares. No personal visitor data.

GET/api/v1/stats?from=&to=The whole agency: totals, per property, per day (30 days by default).
GET/api/v1/listings/{ref}/statsOne property.

6. Webhooks

Events: lead.created, lead.updated (stage changed by the agency), listing.published (publication or renewal), listing.updated, listing.expired (expiry reached, checked daily), listing.archived (archived or reverted to draft), listing.concluded (sold / rented), listing.featured (paid promotion applied), webhook.test. Actions performed from the agency area of MyImmobilier.ch are sent just like those made through the API (within a few seconds); those your CRM triggers itself through the API are not echoed back to it, except listing.published/listing.updated. HTTPS URLs only. Response timeout: 5 s; otherwise a new attempt is made after 1 min, 5 min, 30 min, 2 h, then 12 h (manual replay possible from the agency area). Deduplicate on the X-MyImmobilier-Delivery header / id.

Verify the signature: X-MyImmobilier-Signature: t=<timestamp>,v1=<hex> where v1 = HMAC_SHA256(secret, t + "." + corps_brut). Reject if t is more than 5 minutes old.

import { createHmac, timingSafeEqual } from "node:crypto";

function verifier(secret, enteteSignature, corpsBrut) {
  const { t, v1 } = Object.fromEntries(enteteSignature.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const attendu = createHmac("sha256", secret).update(t + "." + corpsBrut).digest("hex");
  return attendu.length === v1.length && timingSafeEqual(Buffer.from(attendu), Buffer.from(v1));
}

7. Errors and limits

{ "error": { "code": "validation_failed", "message": "…", "details": [{ "chemin": "/characteristics/areaBwf", "message": "must be number" }] } }

Codes: 400 invalid_request, 401 unauthorized, 403 forbidden / agency_not_approved, 404 not_found, 409 invalid_state, 413 payload_too_large, 422 validation_failed. Max body 2 MB (8 MB for imports). A published property expires after 60 days: call renew or receive listing.expired to renew it automatically.

CRM vendor? Write to us for integration support: contact.