{"info":{"_postman_id":"imatch-connect-v1","name":"iMatch Connect API","description":"Partner API for iMatch — Dubai real-estate matchmaking. Authenticate with `Authorization: Bearer imc_live_…` (or `X-Api-Key`). Every response is `{ data, meta }` or `{ error: { code, message, details? } }`. `/v1` is frozen: fields are only ever added. A TEST key (`imc_test_…`) works against the same endpoints in sandbox mode — real reads with masked contacts, simulated writes, synthetic webhook events — and every response to it carries `meta.sandbox: true`.","version":"1.0.0","schema":"https://schema.getpostman.com/json/collection/v2.1.0/collection.json"},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]},"variable":[{"key":"baseUrl","value":"https://api.imatch.ae/v1","type":"string"},{"key":"apiKey","value":"","type":"string","description":"Your imc_live_… key (never commit it)"}],"item":[{"name":"Account","item":[{"name":"Who am I — verify a key and read limits + usage","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/me","host":["{{baseUrl}}"],"path":["me"]},"description":"Requires any active key."},"response":[]},{"name":"This document","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/openapi.json","host":["{{baseUrl}}"],"path":["openapi.json"]},"auth":{"type":"noauth"}},"response":[]},{"name":"Postman collection (v2.1) generated from this document","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/postman.json","host":["{{baseUrl}}"],"path":["postman.json"]},"description":"Import into Postman: one folder per tag, every operation with its parameters and an example body, bearer auth at collection level. Set the `apiKey` collection variable to your key; `baseUrl` defaults to the production server.","auth":{"type":"noauth"}},"response":[]}]},{"name":"Listings","item":[{"name":"Search live listings","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/listings","host":["{{baseUrl}}"],"path":["listings"],"query":[{"key":"as_agent","value":"","description":"Act as the person behind this agent link, so ownership-aware reads return THEIR rows and the Copilot is theirs. The header `X-Connect-Agent` is accepted instead. A link id that is not yours, was revoked, whose iMatch account was deactivated, whose brokerage is no longer yours, or that is not a plain integer → 403 `agent_link_invalid`. A link the person granted narrower scopes than this call needs → 403 `agent_link_scope_denied`.","disabled":true},{"key":"page","value":"","description":"1-based page, default 1","disabled":true},{"key":"per_page","value":"","description":"1–100, default 25","disabled":true},{"key":"q","value":"","description":"Free text over reference, project, community, area, description","disabled":true},{"key":"purpose","value":"","description":"Sale | Rent","disabled":true},{"key":"property_type","value":"","disabled":true},{"key":"area","value":"","disabled":true},{"key":"community","value":"","disabled":true},{"key":"project","value":"","disabled":true},{"key":"developer","value":"","disabled":true},{"key":"bedrooms","value":"","description":"studio | 1 | 2 | … (exact); `5+` means five or more","disabled":true},{"key":"bathrooms","value":"","disabled":true},{"key":"min_price","value":"","disabled":true},{"key":"max_price","value":"","disabled":true},{"key":"min_sqft","value":"","disabled":true},{"key":"max_sqft","value":"","disabled":true},{"key":"furnished","value":"","disabled":true},{"key":"completion_status","value":"","description":"Ready | Off-Plan","disabled":true},{"key":"category","value":"","description":"Normal | Distress — only honoured with distress_read","disabled":true},{"key":"source","value":"","disabled":true},{"key":"updated_since","value":"","description":"ISO 8601 date or date-time, e.g. 2026-09-01T00:00:00Z","disabled":true},{"key":"created_since","value":"","description":"ISO 8601 date or date-time, e.g. 2026-09-01T00:00:00Z","disabled":true},{"key":"external_id","value":"","description":"Your own id (only your rows)","disabled":true},{"key":"ids","value":"","description":"Comma-separated iMatch ids (max 100)","disabled":true},{"key":"sort","value":"","description":"Prefix with - for descending. Default -updated_at","disabled":true},{"key":"include","value":"","description":"Comma-separated extras. `contact` adds agent.phone (charged as a reveal).","disabled":true}]},"description":"Most listings in the market carry no photo of their own. When one does not and we can match its project to our catalogue, that project's photos are appended to `photos` with `kind: \"project\"` — those are pictures of the BUILDING or development, not of this unit. `project_id` and `project_photo_url` are returned on every listing we could match, with or without its own photos, so you can label the picture honestly or link through to the project.\n\nRequires the `listings_read` feature switch."},"response":[]},{"name":"Create or update listings (upsert on external_id)","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/listings","host":["{{baseUrl}}"],"path":["listings"]},"body":{"mode":"raw","raw":"{\n  \"listings\": [\n    {\n      \"external_id\": \"CRM-1001\",\n      \"purpose\": \"Sale\",\n      \"property_type\": \"Apartment\",\n      \"area\": \"Dubai Marina\",\n      \"project\": \"Marina Gate\",\n      \"bedrooms\": 2,\n      \"bathrooms\": 2,\n      \"bua_sqft\": 1250,\n      \"selling_price\": 2500000,\n      \"furnished\": \"Unfurnished\",\n      \"completion_status\": \"Ready\",\n      \"view\": \"Marina\",\n      \"description\": \"Bright two-bed with full marina view, vacant on transfer.\",\n      \"agent_name\": \"Sara Khan\",\n      \"agent_phone\": \"+971500000001\"\n    }\n  ]\n}","options":{"raw":{"language":"json"}}},"description":"One listing object or `{ listings: [...] }` (max 50). Re-sending the same `external_id` updates the row. 200 when every item succeeded, 207 when some failed.\n\nRequires the `listings_write` feature switch."},"response":[]},{"name":"One listing by iMatch id","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/listings/:id","host":["{{baseUrl}}"],"path":["listings",":id"],"query":[{"key":"include","value":"","description":"Comma-separated. `contact` adds `agent.phone` and is charged as a reveal.","disabled":true}],"variable":[{"key":"id","value":"","description":"Path parameter id"}]},"description":"Requires the `listings_read` feature switch."},"response":[]},{"name":"One of your listings by your own external id","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/listings/by-external/:external_id","host":["{{baseUrl}}"],"path":["listings","by-external",":external_id"],"query":[{"key":"include","value":"","description":"Comma-separated. `contact` adds `agent.phone` and is charged as a reveal.","disabled":true}],"variable":[{"key":"external_id","value":"","description":"Path parameter external_id"}]},"description":"Requires the `listings_read` feature switch."},"response":[]},{"name":"Partially update one of your listings","request":{"method":"PATCH","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/listings/by-external/:external_id","host":["{{baseUrl}}"],"path":["listings","by-external",":external_id"],"variable":[{"key":"external_id","value":"","description":"Path parameter external_id"}]},"body":{"mode":"raw","raw":"{\n  \"selling_price\": 2400000,\n  \"live_status\": \"Active\"\n}","options":{"raw":{"language":"json"}}},"description":"`live_status: \"Sold\"` also sets `status` to Sold. Only rows you created through Connect.\n\nRequires the `listings_write` feature switch."},"response":[]},{"name":"Unlist one of your listings (never deletes)","request":{"method":"DELETE","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/listings/by-external/:external_id","host":["{{baseUrl}}"],"path":["listings","by-external",":external_id"],"variable":[{"key":"external_id","value":"","description":"Path parameter external_id"}]},"description":"Requires the `listings_write` feature switch."},"response":[]},{"name":"Attach photos / floor plans to one of your listings","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/listings/:id/photos","host":["{{baseUrl}}"],"path":["listings",":id","photos"],"variable":[{"key":"id","value":"","description":"Path parameter id"}]},"body":{"mode":"raw","raw":"{\n  \"photos\": [\n    {\n      \"url\": \"https://cdn.example.com/photos/1001-1.jpg\",\n      \"kind\": \"photo\"\n    },\n    {\n      \"url\": \"https://cdn.example.com/photos/1001-plan.png\",\n      \"kind\": \"floor_plan\"\n    }\n  ]\n}","options":{"raw":{"language":"json"}}},"description":"Up to 20 per call, each a public `url` or `base64` (image/*, ≤15 MB). Stored re-encoded as WebP. **Idempotent:** a photo already on the listing — the same `url` and `kind`, or the same image — is not added again and not downloaded again; the response still has one entry per photo you sent, in your order, pointing at the existing photo. `meta.added` counts what was really stored, `meta.skipped_duplicates` what was already there. A new photo keeps the `url` you sent as `source_url`; an image already on the listing under another url keeps its original `source_url` (use `PUT` to re-point it). If any photo fails, nothing is stored and `error.details.failed` names every one: `[{ index, url, reason, message }]`, `reason` ∈ `fetch_failed` · `timeout` · `unsupported_media` · `too_large` · `decode_failed` · `invalid`.\n\nRequires the `photos_write` feature switch."},"response":[]},{"name":"Replace a listing's photos with exactly this list","request":{"method":"PUT","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/listings/:id/photos","host":["{{baseUrl}}"],"path":["listings",":id","photos"],"variable":[{"key":"id","value":"","description":"Path parameter id"}]},"body":{"mode":"raw","raw":"{\n  \"photos\": [\n    {\n      \"url\": \"https://cdn.example.com/photos/1001-1.jpg\",\n      \"kind\": \"photo\"\n    },\n    {\n      \"url\": \"https://cdn.example.com/photos/1001-plan.png\",\n      \"kind\": \"floor_plan\"\n    }\n  ]\n}","options":{"raw":{"language":"json"}}},"description":"Needs `photos_write` **and** `photos_replace` (off by default — check `GET /v1/me`). Makes the listing's photo set EXACTLY the list you send, in that order (0–100 photos; `[]` removes them all). For each photo, in order:\n\n1. a photo already on the listing with the same `url` **and** `kind` is **kept** — it is not downloaded again, and keeps its `id`;\n2. otherwise the `url` is fetched and re-encoded, and a photo already on the listing with the same image (same `kind`) is **kept** and learns your `url` as its `source_url` — this is how photos attached before `source_url` existed are matched, and how old duplicate copies disappear;\n3. otherwise it is **added**.\n\nEvery photo the listing held that was not kept is **removed**. A `url` that changes `kind` (photo → floor_plan) is a remove + add with a new `id`, never a kept photo. The same `url` twice in one body, or two urls that are the same image, is a `400 validation_error`.\n\n**All or nothing:** every photo is fetched before anything is written; if any fails, nothing changes and `error.details.failed` names each one (`index`, `url`, `reason`). Two calls on one listing never interleave. **Latency:** synchronous; at most 10 downloads run at once, each capped at 10 s plus a few seconds to re-encode, so the worst case is about 130 s for 100 new photos — typically seconds, and a repeat of an unchanged set downloads nothing. Set your client timeout to 180 s. **Not a listing write:** it does not refresh the listing's 21-day expiry and emits no webhook, exactly like `POST …/photos` and `DELETE …/photos/{photoId}`. Response `meta`: `kept`, `added`, `removed`. After `[]`, a read of the listing may show `kind: \"project\"` photos again — those are iMatch's pictures of the building, never yours, and are not affected by a replace.\n\nRequires the `photos_replace` feature switch."},"response":[]},{"name":"Replace the photos of one of your listings, by your own external id","request":{"method":"PUT","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/listings/by-external/:external_id/photos","host":["{{baseUrl}}"],"path":["listings","by-external",":external_id","photos"],"variable":[{"key":"external_id","value":"","description":"Path parameter external_id"}]},"body":{"mode":"raw","raw":"{\n  \"photos\": [\n    {\n      \"url\": \"https://cdn.example.com/photos/1001-1.jpg\",\n      \"kind\": \"photo\"\n    },\n    {\n      \"url\": \"https://cdn.example.com/photos/1001-plan.png\",\n      \"kind\": \"floor_plan\"\n    }\n  ]\n}","options":{"raw":{"language":"json"}}},"description":"Needs `photos_write` **and** `photos_replace` (off by default — check `GET /v1/me`). Makes the listing's photo set EXACTLY the list you send, in that order (0–100 photos; `[]` removes them all). For each photo, in order:\n\n1. a photo already on the listing with the same `url` **and** `kind` is **kept** — it is not downloaded again, and keeps its `id`;\n2. otherwise the `url` is fetched and re-encoded, and a photo already on the listing with the same image (same `kind`) is **kept** and learns your `url` as its `source_url` — this is how photos attached before `source_url` existed are matched, and how old duplicate copies disappear;\n3. otherwise it is **added**.\n\nEvery photo the listing held that was not kept is **removed**. A `url` that changes `kind` (photo → floor_plan) is a remove + add with a new `id`, never a kept photo. The same `url` twice in one body, or two urls that are the same image, is a `400 validation_error`.\n\n**All or nothing:** every photo is fetched before anything is written; if any fails, nothing changes and `error.details.failed` names each one (`index`, `url`, `reason`). Two calls on one listing never interleave. **Latency:** synchronous; at most 10 downloads run at once, each capped at 10 s plus a few seconds to re-encode, so the worst case is about 130 s for 100 new photos — typically seconds, and a repeat of an unchanged set downloads nothing. Set your client timeout to 180 s. **Not a listing write:** it does not refresh the listing's 21-day expiry and emits no webhook, exactly like `POST …/photos` and `DELETE …/photos/{photoId}`. Response `meta`: `kept`, `added`, `removed`. After `[]`, a read of the listing may show `kind: \"project\"` photos again — those are iMatch's pictures of the building, never yours, and are not affected by a replace.\n\nRequires the `photos_replace` feature switch."},"response":[]},{"name":"Remove a photo from one of your listings","request":{"method":"DELETE","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/listings/:id/photos/:photoId","host":["{{baseUrl}}"],"path":["listings",":id","photos",":photoId"],"variable":[{"key":"id","value":"","description":"Path parameter id"},{"key":"photoId","value":"","description":"Path parameter photoId"}]},"description":"Requires the `photos_write` feature switch."},"response":[]},{"name":"Distress deals (discounted listings)","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/distress","host":["{{baseUrl}}"],"path":["distress"],"query":[{"key":"page","value":"","description":"1-based page, default 1","disabled":true},{"key":"per_page","value":"","description":"1–100, default 25","disabled":true},{"key":"q","value":"","description":"Free text over reference, project, community, area, description","disabled":true},{"key":"purpose","value":"","description":"Sale | Rent","disabled":true},{"key":"property_type","value":"","disabled":true},{"key":"area","value":"","disabled":true},{"key":"community","value":"","disabled":true},{"key":"project","value":"","disabled":true},{"key":"developer","value":"","disabled":true},{"key":"bedrooms","value":"","description":"studio | 1 | 2 | … (exact); `5+` means five or more","disabled":true},{"key":"bathrooms","value":"","disabled":true},{"key":"min_price","value":"","disabled":true},{"key":"max_price","value":"","disabled":true},{"key":"min_sqft","value":"","disabled":true},{"key":"max_sqft","value":"","disabled":true},{"key":"furnished","value":"","disabled":true},{"key":"completion_status","value":"","description":"Ready | Off-Plan","disabled":true},{"key":"category","value":"","description":"Normal | Distress — only honoured with distress_read","disabled":true},{"key":"source","value":"","disabled":true},{"key":"updated_since","value":"","description":"ISO 8601 date or date-time, e.g. 2026-09-01T00:00:00Z","disabled":true},{"key":"created_since","value":"","description":"ISO 8601 date or date-time, e.g. 2026-09-01T00:00:00Z","disabled":true},{"key":"external_id","value":"","description":"Your own id (only your rows)","disabled":true},{"key":"ids","value":"","description":"Comma-separated iMatch ids (max 100)","disabled":true},{"key":"sort","value":"","description":"Prefix with - for descending. Default -updated_at","disabled":true},{"key":"include","value":"","description":"Comma-separated extras. `contact` adds agent.phone (charged as a reveal).","disabled":true}]},"description":"Same filters as `/listings`; `category` is forced to Distress and the discount fields are always present. Default sort: biggest discount first. Project photos and `project_id` / `project_photo_url` work exactly as on `/listings` — a `kind: \"project\"` photo is the building, not this unit.\n\nRequires the `distress_read` feature switch."},"response":[]}]},{"name":"Inquiries","item":[{"name":"Search live buyer inquiries","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/inquiries","host":["{{baseUrl}}"],"path":["inquiries"],"query":[{"key":"page","value":"","description":"1-based page, default 1","disabled":true},{"key":"per_page","value":"","description":"1–100, default 25","disabled":true},{"key":"q","value":"","description":"Free text over reference, location, project, requirements","disabled":true},{"key":"purpose","value":"","disabled":true},{"key":"location","value":"","description":"Matches location_wanted / preferred_communities","disabled":true},{"key":"project","value":"","disabled":true},{"key":"property_type","value":"","disabled":true},{"key":"bedrooms","value":"","disabled":true},{"key":"min_budget","value":"","disabled":true},{"key":"max_budget","value":"","disabled":true},{"key":"updated_since","value":"","description":"ISO 8601 date or date-time, e.g. 2026-09-01T00:00:00Z","disabled":true},{"key":"created_since","value":"","description":"ISO 8601 date or date-time, e.g. 2026-09-01T00:00:00Z","disabled":true},{"key":"source","value":"","disabled":true},{"key":"external_id","value":"","disabled":true},{"key":"ids","value":"","description":"Comma-separated iMatch ids (max 100)","disabled":true},{"key":"sort","value":"","disabled":true},{"key":"include","value":"","description":"Comma-separated extras. `contact` adds agent.phone (charged as a reveal).","disabled":true}]},"description":"Requires the `inquiries_read` feature switch."},"response":[]},{"name":"Create or update inquiries (upsert on external_id)","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/inquiries","host":["{{baseUrl}}"],"path":["inquiries"]},"body":{"mode":"raw","raw":"{\n  \"inquiries\": [\n    {\n      \"external_id\": \"LEAD-77\",\n      \"purpose\": \"Sale\",\n      \"location_wanted\": \"Dubai Marina\",\n      \"property_type_wanted\": \"Apartment\",\n      \"bedrooms_wanted\": \"2\",\n      \"budget_min\": 2000000,\n      \"budget_max\": 2600000,\n      \"preferred_status\": \"Ready\",\n      \"agent_name\": \"Sara Khan\",\n      \"agent_phone\": \"+971500000001\"\n    }\n  ]\n}","options":{"raw":{"language":"json"}}},"description":"One inquiry or `{ inquiries: [...] }` (max 50). Needs `purpose` and `location_wanted` or `project_wanted`. A new Active inquiry is matched against live listings automatically.\n\nRequires the `inquiries_write` feature switch."},"response":[]},{"name":"One inquiry by iMatch id","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/inquiries/:id","host":["{{baseUrl}}"],"path":["inquiries",":id"],"query":[{"key":"include","value":"","description":"Comma-separated. `contact` adds `agent.phone` and is charged as a reveal.","disabled":true}],"variable":[{"key":"id","value":"","description":"Path parameter id"}]},"description":"Requires the `inquiries_read` feature switch."},"response":[]},{"name":"One of your inquiries by your own external id","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/inquiries/by-external/:external_id","host":["{{baseUrl}}"],"path":["inquiries","by-external",":external_id"],"query":[{"key":"include","value":"","description":"Comma-separated. `contact` adds `agent.phone` and is charged as a reveal.","disabled":true}],"variable":[{"key":"external_id","value":"","description":"Path parameter external_id"}]},"description":"Requires the `inquiries_read` feature switch."},"response":[]},{"name":"Partially update one of your inquiries","request":{"method":"PATCH","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/inquiries/by-external/:external_id","host":["{{baseUrl}}"],"path":["inquiries","by-external",":external_id"],"variable":[{"key":"external_id","value":"","description":"Path parameter external_id"}]},"body":{"mode":"raw","raw":"{\n  \"budget_max\": 2800000\n}","options":{"raw":{"language":"json"}}},"description":"Requires the `inquiries_write` feature switch."},"response":[]},{"name":"Unlist one of your inquiries (never deletes)","request":{"method":"DELETE","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/inquiries/by-external/:external_id","host":["{{baseUrl}}"],"path":["inquiries","by-external",":external_id"],"variable":[{"key":"external_id","value":"","description":"Path parameter external_id"}]},"description":"Requires the `inquiries_write` feature switch."},"response":[]}]},{"name":"Projects","item":[{"name":"Dubai projects feed","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/projects","host":["{{baseUrl}}"],"path":["projects"],"query":[{"key":"page","value":"","description":"1-based page, default 1","disabled":true},{"key":"per_page","value":"","description":"1–100, default 25","disabled":true},{"key":"q","value":"","description":"Search text. With sort=relevance (the default when q is given) every word must appear in the name, an alias, the district, the area or the developer, best match first. With any other sort: name or alias contains q.","disabled":true},{"key":"developer","value":"","disabled":true},{"key":"area","value":"","disabled":true},{"key":"district","value":"","disabled":true},{"key":"project_type","value":"","disabled":true},{"key":"property_type","value":"","disabled":true},{"key":"status","value":"","disabled":true},{"key":"sale_status","value":"","disabled":true},{"key":"completion_status","value":"","disabled":true},{"key":"min_price","value":"","disabled":true},{"key":"max_price","value":"","disabled":true},{"key":"updated_since","value":"","description":"ISO 8601 date or date-time, e.g. 2026-09-01T00:00:00Z","disabled":true},{"key":"sort","value":"","description":"relevance (default when q is given; without q it lists by name) or name (default without q), min_price, expected_completion_date; prefix - for descending","disabled":true}]},"description":"Requires the `projects_read` feature switch."},"response":[]},{"name":"Project detail","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/projects/:id","host":["{{baseUrl}}"],"path":["projects",":id"],"query":[{"key":"include","value":"","description":"`units`","disabled":true}],"variable":[{"key":"id","value":"","description":"Path parameter id"}]},"description":"`include=units` adds the developer's current unit list and requires the `units_read` feature.\n\nRequires the `projects_read` feature switch."},"response":[]}]},{"name":"Locations","item":[{"name":"Areas with aliases","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/areas","host":["{{baseUrl}}"],"path":["areas"]},"description":"Requires the `areas_read` feature switch."},"response":[]},{"name":"Community hierarchy","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/communities","host":["{{baseUrl}}"],"path":["communities"]},"description":"Requires the `areas_read` feature switch."},"response":[]},{"name":"Search places in the locations tree","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/locations/search?q=","host":["{{baseUrl}}"],"path":["locations","search"],"query":[{"key":"q","value":"","description":"What the user typed — a name, an abbreviation (`JVC`) or its start","disabled":false},{"key":"limit","value":"","description":"Default 12, max 30","disabled":true},{"key":"level","value":"","description":"Comma list of levels to keep, e.g. `area,community`","disabled":true},{"key":"under","value":"","description":"Only places inside this place id","disabled":true}]},"description":"Every place in the UAE sits in one tree: city → region → area → community → sub-community → cluster → building. Results are ranked: the whole query IS a name or an alias (`JVC`) → a name starting with it → a word starting with it → a close spelling. Each place carries its `breadcrumb` (the places it sits in, the city first), so a picker can show where it is. `meta.exact` is true when a name or alias matched the whole query. Area ids are the same ids `/v1/areas` returns. Client-cacheable for five minutes (`Cache-Control: private, max-age=300`).\n\nRequires the `areas_read` feature switch."},"response":[]},{"name":"The top of the locations tree","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/locations","host":["{{baseUrl}}"],"path":["locations"]},"description":"Every place that sits in nothing — the eight emirate cities — A–Z. The starting point of a walk: continue with `/locations/{id}/children`, or take the whole tree at once with `/locations/export`. Client-cacheable for five minutes (`Cache-Control: private, max-age=300`).\n\nRequires the `areas_read` feature switch."},"response":[]},{"name":"The whole locations tree, paged","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/locations/export","host":["{{baseUrl}}"],"path":["locations","export"],"query":[{"key":"cursor","value":"","description":"`next_cursor` from the previous page","disabled":true},{"key":"limit","value":"","description":"Places per page. Default 1000, max 2000","disabled":true}]},"description":"Parents before children (depth, then id), so every `parent_id` on a page was served on an earlier page or this one. Walk with `meta.next_cursor` until it is null. `meta.scope` says what you get: **compact** — every live place (the place fields plus `aliases`); **full** — with the `locations_refs` feature: EVERY node (merged signposts, retired places, schools and nurseries too) with every column we hold, every alias row with its `kind`, and every outside id (`refs`: Bayut, DLD, legacy — primary and secondary). Every page carries `ETag` = the version of what you see (`meta.version` too): send it back as `If-None-Match` and an unchanged tree answers `304 Not Modified` with no body. If the version on your last page differs from your first, the tree changed while you walked — walk again.\n\nRequires the `areas_read` feature switch."},"response":[]},{"name":"The places that changed since a point — incremental sync of the tree","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/locations/changes","host":["{{baseUrl}}"],"path":["locations","changes"],"query":[{"key":"since","value":"","description":"Where to start: an ISO 8601 date-time, or a `next_cursor` you were given. Ignored when `cursor` is sent.","disabled":true},{"key":"cursor","value":"","description":"`meta.next_cursor` from your previous call — continue exactly where it stopped","disabled":true},{"key":"limit","value":"","description":"Places per page. Default 500, max 2000","disabled":true}]},"description":"Take `/locations/export` once, then keep your copy current with this feed. Every item is `{ id, change, at, merged_into_id, place }`: `change` is `created`, `updated` (the place, one of its aliases or outside ids, or a place above it — its breadcrumb — changed) or `deleted` (no longer a live place: switched off, or merged — `merged_into_id` says where; re-point what you stored). `place` is the place exactly as the export shows it: every field with the `locations_refs` feature (merged and retired nodes as they are, schools and nurseries included), the compact place otherwise (null when it is no longer a live place; compact never lists schools or nurseries). Ordered by `(updated_at, id)` with a strict cursor — nothing skipped or repeated. **Ten-minute safety lag:** changes from the last 10 minutes are held back and `next_cursor` never passes `now − 10 min` (`meta.until`), because a place-writing batch can run for minutes and its rows carry its start time. Start with `since` (an ISO date-time, or any cursor we issued); continue with `cursor` = `meta.next_cursor` (it wins when both are sent). With neither, the feed starts from the tree's beginning — prefer the export for a first copy. `GET /changes?types=locations` is the ids-only view of the same rows; the cursors are interchangeable. Read-only: the tree is never modified through the API.\n\nRequires the `areas_read` feature switch."},"response":[]},{"name":"Place ids that no longer answer as a place of their own","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/locations/retired","host":["{{baseUrl}}"],"path":["locations","retired"]},"description":"Merged into another place (`merged_into` = the live survivor — re-point what you stored) or taken out of the tree (`merged_into: null`). The companion of the compact export for a CRM that stores our ids. Same version and conditional GET as the export's tree.\n\nRequires the `areas_read` feature switch."},"response":[]},{"name":"Where a listing with this location text would be placed","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/locations/resolve","host":["{{baseUrl}}"],"path":["locations","resolve"],"query":[{"key":"area","value":"","description":"The listing's area text, exactly as you would send it to POST /listings","disabled":true},{"key":"community","value":"","description":"The listing's community text","disabled":true},{"key":"project","value":"","description":"The listing's project or building text","disabled":true},{"key":"within","value":"","description":"Only look inside this place id","disabled":true}]},"description":"The same reading `POST /listings` makes of the same `area` / `community` / `project` (when no `location_id` is sent): our area vocabulary, then the database's resolver — exact names, aliases and catalogue projects, Dubai first, never a fuzzy guess. `status` is `resolved` (one place), `ambiguous` (several fit — `place` is the one iMatch would choose, null when it would not) or `unresolved`. `conflicting_fields` names fields that point somewhere else — usually a text worth fixing. Check a placement before you push, or map your own location list once.\n\nRequires the `areas_read` feature switch."},"response":[]},{"name":"Our place for an outside location id","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/locations/by-ref/:source/:external_id","host":["{{baseUrl}}"],"path":["locations","by-ref",":source",":external_id"],"variable":[{"key":"source","value":"","description":"Path parameter source"},{"key":"external_id","value":"","description":"Path parameter external_id"}]},"description":"A Bayut location id, a Dubai Land Department area / master project / project / building id, or the id of one of our older lists (`legacy_area` = `/areas` id, `legacy_community` = `/communities` id, `legacy_project`) → the place, with every field. A place merged since answers as its survivor. Off unless iMatch enabled `locations_refs` for you.\n\nRequires the `locations_refs` feature switch."},"response":[]},{"name":"One place, the places it sits in, and the places inside it","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/locations/:id","host":["{{baseUrl}}"],"path":["locations",":id"],"variable":[{"key":"id","value":"","description":"Path parameter id"}]},"description":"`node` is the place and its `aliases` (the other texts iMatch reads as it). With the `locations_refs` feature it is the FULL place: every column we hold, every alias row with its kind, every outside id — and any node the full export carries (a school, a retired place) answers too. `children` is the first page of the places directly inside, A–Z — continue with `/locations/{id}/children`. A merged place answers as the place it was merged into, with `meta.merged_from` set to the id you asked for — store the new id.\n\nRequires the `areas_read` feature switch."},"response":[]},{"name":"The places directly inside a place","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/locations/:id/children","host":["{{baseUrl}}"],"path":["locations",":id","children"],"query":[{"key":"cursor","value":"","description":"`next_cursor` from the previous page","disabled":true},{"key":"limit","value":"","description":"Default 50, max 100","disabled":true}],"variable":[{"key":"id","value":"","description":"Path parameter id"}]},"description":"A–Z, keyset-paged: pass `meta.next_cursor` back as `cursor` until it is null. A merged id pages the place it was merged into (`meta.merged_from`).\n\nRequires the `areas_read` feature switch."},"response":[]}]},{"name":"Matches","item":[{"name":"Matches on your own listings and inquiries","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/matches","host":["{{baseUrl}}"],"path":["matches"],"query":[{"key":"page","value":"","description":"1-based page, default 1","disabled":true},{"key":"per_page","value":"","description":"1–100, default 25","disabled":true},{"key":"since","value":"","description":"Only matches created at/after this instant","disabled":true},{"key":"status","value":"","disabled":true},{"key":"side","value":"","description":"Which side of the match is yours","disabled":true},{"key":"listing_id","value":"","disabled":true},{"key":"inquiry_id","value":"","disabled":true},{"key":"min_score","value":"","disabled":true},{"key":"include","value":"","description":"Comma-separated extras. `contact` adds the other party's agent.phone (charged as a reveal). `nearby` adds NEARBY matches (the right area, not the exact building) — left out unless you ask; each match says which it is in `kind`.","disabled":true}]},"description":"EXACT matches only unless you send `include=nearby`: then NEARBY matches too (the right area, not the exact building), each marked `kind: \"nearby\"` / `is_nearby: true` — `meta.total` counts what you asked for. Show nearby matches apart from exact ones.\n\nRequires the `matches_read` feature switch."},"response":[]}]},{"name":"Changes","item":[{"name":"What changed since your last call (ids only)","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/changes","host":["{{baseUrl}}"],"path":["changes"],"query":[{"key":"since","value":"","description":"The `next_cursor` from your previous call, or an ISO 8601 date-time to start from.","disabled":true},{"key":"types","value":"","description":"Comma-separated: listings, inquiries, projects, locations. Default: listings, inquiries and projects you may read — `locations` only when you ask for it.","disabled":true},{"key":"limit","value":"","description":"Max rows per type, default and ceiling 500","disabled":true}]},"description":"Each requested type needs its own read feature (`listings_read`, `inquiries_read`, `projects_read`, `areas_read` for `locations`); with no `types` you get every one of listings / inquiries / projects you may read — `locations` only when you ask for it. Rows are ordered by `(updated_at, id)` and the cursor is strict, so nothing is skipped or repeated under concurrent writes. Safety lag: changes from the last 10 seconds are held back and `next_cursor` never advances past `now − 10 s`, so a write that took a few seconds to commit is still picked up on your next call — expect a row to appear at least 10 seconds after it changed, never before. Pass `meta.next_cursor` back as `since`; keep polling with the same cursor when nothing changed. `change` is one of created / updated / unlisted / sold / deleted — fetch the row through the read API for its fields. `types=locations` (2026-09-26): the places of the tree, `change` created / updated / deleted with `merged_into_id` on every row, held TEN MINUTES back instead of 10 seconds (a place-writing batch can run for minutes) — `GET /locations/changes` is the same feed with each place in full. Listings and inquiries you are not licensed to read (`allowed_sources`) never appear.\n\nRequires any active key."},"response":[]}]},{"name":"Webhooks","item":[{"name":"Your webhooks","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/webhooks","host":["{{baseUrl}}"],"path":["webhooks"]},"description":"Requires the `webhooks` feature switch."},"response":[]},{"name":"Create a webhook (the signing secret is returned ONCE)","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/webhooks","host":["{{baseUrl}}"],"path":["webhooks"]},"body":{"mode":"raw","raw":"{\n  \"url\": \"https://crm.example.com/imatch/webhook\",\n  \"events\": [\n    \"match.created\",\n    \"listing.updated\",\n    \"inquiry.created\"\n  ],\n  \"description\": \"Production receiver\"\n}","options":{"raw":{"language":"json"}}},"description":"We POST a JSON envelope `{ id, event, created_at, partner, data }` with headers `X-Connect-Event`, `X-Connect-Delivery-Id` and `X-Connect-Signature: t=<unix seconds>,v1=<hex>` where `v1 = HMAC-SHA256(secret, t + \".\" + rawBody)`. Verify in constant time and reject `t` older than 5 minutes. Respond 2xx within 10 s; anything else is retried at 1m, 5m, 30m, 2h and 12h, then marked failed. Fifty consecutive failed deliveries switch the webhook off. Deliveries are at-least-once — dedupe on `id`.\n\nEvents: `ping` — A test delivery you asked for. · `match.created` — A new match on one of your listings or inquiries — exact matches; nearby ones too only with the matches_nearby feature. · `listing.created` — One of your listings was created through Connect. · `listing.updated` — One of your listings was changed through Connect, or put back on the market. · `listing.unlisted` — One of your listings was taken off the market — by you, by expiry, or by iMatch. · `listing.sold` — One of your listings was marked sold — by you, or by iMatch. · `inquiry.created` — One of your inquiries was created through Connect. · `inquiry.updated` — One of your inquiries was changed through Connect, or re-opened. · `inquiry.unlisted` — One of your inquiries was closed — by you, by expiry, or by iMatch. · `distress.created` — A new distress deal went live (needs the distress feature). · `agent_link.connected` — One of your users connected their iMatch account. · `agent_link.revoked` — An agent link ended — by the agent, by you, by their brokerage, by iMatch, or because the account was deactivated. · `location.updated` — Places in the locations tree changed — created, renamed, moved, re-aliased, merged or retired (needs areas_read).\n\nRequires the `webhooks` feature switch."},"response":[]},{"name":"Change a webhook, or rotate its secret","request":{"method":"PATCH","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/webhooks/:id","host":["{{baseUrl}}"],"path":["webhooks",":id"],"variable":[{"key":"id","value":"","description":"Path parameter id"}]},"body":{"mode":"raw","raw":"{\n  \"active\": true\n}","options":{"raw":{"language":"json"}}},"description":"`rotate_secret: true` mints a new secret and returns it ONCE; the old one stops working immediately. Setting `active: true` also resets the consecutive-failure counter.\n\nRequires the `webhooks` feature switch."},"response":[]},{"name":"Delete a webhook and its delivery history","request":{"method":"DELETE","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/webhooks/:id","host":["{{baseUrl}}"],"path":["webhooks",":id"],"variable":[{"key":"id","value":"","description":"Path parameter id"}]},"description":"Requires the `webhooks` feature switch."},"response":[]},{"name":"Send a test `ping` to a webhook","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/webhooks/:id/test","host":["{{baseUrl}}"],"path":["webhooks",":id","test"],"variable":[{"key":"id","value":"","description":"Path parameter id"}]},"description":"Queues one `ping` delivery to this webhook regardless of its subscriptions. Poll `GET /webhooks/deliveries/{delivery_id}` for the outcome (status, response code, error).\n\nRequires the `webhooks` feature switch."},"response":[]},{"name":"Delivery log","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/webhooks/deliveries","host":["{{baseUrl}}"],"path":["webhooks","deliveries"],"query":[{"key":"page","value":"","description":"1-based page, default 1","disabled":true},{"key":"per_page","value":"","description":"1–100, default 25","disabled":true},{"key":"webhook_id","value":"","disabled":true},{"key":"status","value":"","disabled":true},{"key":"event","value":"","description":"One event name (`*` is not a filter)","disabled":true},{"key":"since","value":"","description":"Only deliveries created at/after this instant","disabled":true}]},"description":"Newest first. The list omits `payload`; the detail endpoint includes it.\n\nRequires the `webhooks` feature switch."},"response":[]},{"name":"One delivery, with the envelope exactly as sent","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/webhooks/deliveries/:id","host":["{{baseUrl}}"],"path":["webhooks","deliveries",":id"],"variable":[{"key":"id","value":"","description":"Path parameter id"}]},"description":"Requires the `webhooks` feature switch."},"response":[]},{"name":"Send a delivery again","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/webhooks/deliveries/:id/redeliver","host":["{{baseUrl}}"],"path":["webhooks","deliveries",":id","redeliver"],"variable":[{"key":"id","value":"","description":"Path parameter id"}]},"description":"Resets the delivery to pending with a fresh retry budget and queues it. A delivery that succeeded less than a minute ago is refused.\n\nRequires the `webhooks` feature switch."},"response":[]}]},{"name":"Embeds","item":[{"name":"Mint an embed token for our tools and screens","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/embed/sessions","host":["{{baseUrl}}"],"path":["embed","sessions"]},"body":{"mode":"raw","raw":"{\n  \"tools\": [\n    \"offer\",\n    \"poster\"\n  ],\n  \"agent\": {\n    \"name\": \"Sara Khan\",\n    \"phone\": \"+971500000001\",\n    \"external_id\": \"U-12\"\n  },\n  \"listing\": {\n    \"external_id\": \"CRM-1001\"\n  },\n  \"ttl_sec\": 900\n}","options":{"raw":{"language":"json"}}},"description":"Call this from YOUR SERVER with your API key and hand the returned `token` to the browser — the key itself never reaches a page. `tools` names what the token may open: the creators `offer` and `poster` (feature `tools_embed`) and `copilot` (feature `copilot`), and the screens `listings` (`listings_read`), `projects` (`projects_read`) and `matches` (`matches_read`) — the same read switches the matching `/listings`, `/projects` and `/matches` endpoints need; a request naming several kinds must pass every switch. The partner must have at least one allowed origin (Limits → Allowed origins in iMatch Connect) or the call is refused with `no_allowed_origins`. Open `<embed_base>/<tool>?token=<token>` in an iframe on one of those origins, or use the SDK (`/connect/v1/connect.js`). Tokens live at most one hour; mint a new one when the page reloads. `listing.inline` opens the offer / poster tools on a listing that exists only in your CRM (never sent to `/listings`): its details are kept for the session only (never beyond the hour, never in `listings`), photos are fetched from your https URLs at render time, and what the agent saves carries your `external_id` as its reference. The Copilot tool ignores it. See Embeds & SDK → Inline listings. `agent_link_id` opens the session AS a linked person: the screens show their own data and the Copilot is theirs, continuing the same thread they see in the iMatch app and on WhatsApp. The `agent` block stays display-only either way — a name your CRM sends is not a login. See Agent links.\n\nRequires any active key."},"response":[]}]},{"name":"AI","item":[{"name":"Ask the iMatch Copilot (one turn)","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/copilot/chat","host":["{{baseUrl}}"],"path":["copilot","chat"]},"body":{"mode":"raw","raw":"{\n  \"conversation_id\": \"crm-thread-42\",\n  \"message\": \"Any two-bed under 2.5M in Dubai Marina?\",\n  \"context\": {\n    \"external_id\": \"CRM-9\"\n  }\n}","options":{"raw":{"language":"json"}}},"description":"Runs the same Copilot brain the iMatch panel uses, as your partner identity: it searches and explains listings, buyer inquiries, projects, areas and DLD market data across the market. It is READ-ONLY here — it cannot create or change records, send WhatsApp messages, generate files or read account profiles (write through the REST API or the MCP write tools). Conversations are kept for 24 h from the last message, keyed by your partner + `conversation_id`; omit the id to start one, or pass your own. `actions` lists the records the answer is about with their REST URLs. `usage` reports tool calls and replayed history, not tokens. Separate budget: 30 messages per minute per partner (`rate_limited` with `Retry-After`).\n\nRequires the `copilot` feature switch."},"response":[]},{"name":"Ask the iMatch Copilot — Server-Sent Events","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/copilot/chat/stream","host":["{{baseUrl}}"],"path":["copilot","chat","stream"]},"body":{"mode":"raw","raw":"{\n  \"conversation_id\": \"crm-thread-42\",\n  \"message\": \"And which of those has a sea view?\"\n}","options":{"raw":{"language":"json"}}},"description":"Same request and same brain as `/copilot/chat`, delivered as `text/event-stream`. Frames: `: keepalive` comments every 10 s while tools run; `event: action` (one per record the answer is about); `event: token` (`{ text }`, sentence-sized pieces of the reply); `event: done` (the full `/copilot/chat` data object); `event: error` (`{ code, message }`). Today the brain answers in one piece, so `token` frames arrive together at the end of the turn — the stream's value is the keepalive during tool use; when the brain streams tokens the frames become progressive without a contract change. A bad body is a normal 400 envelope, not a stream.\n\nRequires the `copilot` feature switch."},"response":[]},{"name":"MCP server — JSON-RPC 2.0 over Streamable HTTP (for Claude, ChatGPT, Cursor, …)","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/mcp","host":["{{baseUrl}}"],"path":["mcp"]},"body":{"mode":"raw","raw":"{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"search_listings\",\n    \"arguments\": {\n      \"area\": \"Dubai Marina\",\n      \"bedrooms\": \"2\",\n      \"purpose\": \"Sale\",\n      \"per_page\": 5\n    }\n  }\n}","options":{"raw":{"language":"json"}}},"description":"Model Context Protocol, protocol version `2025-06-18`. Methods: `initialize`, `notifications/initialized`, `ping`, `tools/list`, `tools/call`; batch arrays are accepted (at most 20 messages; every message counts as one request against the key's limit and daily quota — once spent, the rest of the batch answers -32004). Responses are plain JSON (no server-initiated stream). Each tool runs the SAME code as the REST route it mirrors and needs the same feature switch — a missing switch is JSON-RPC error `-32003` (`feature_disabled`). Tools: `search_listings` (listings_read), `get_listing` (listings_read), `search_inquiries` (inquiries_read), `get_inquiry` (inquiries_read), `search_projects` (projects_read), `get_project` (projects_read), `list_areas` (areas_read), `search_locations` (areas_read), `get_location` (areas_read), `resolve_location` (areas_read), `get_location_by_ref` (locations_refs), `my_matches` (matches_read), `create_listing` (listings_write), `update_listing` (listings_write), `create_inquiry` (inquiries_write), `distress_deals` (distress_read), `list_webhooks` (webhooks). Configure a client with this URL and `Authorization: Bearer imc_live_…`, e.g. `claude mcp add --transport http imatch https://api.imatch.ae/mcp --header \"Authorization: Bearer <key>\"`.\n\nRequires any active key."},"response":[]},{"name":"MCP — no stream to open; answers 405 with the connection hint","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/mcp","host":["{{baseUrl}}"],"path":["mcp"]},"description":"Requires any active key."},"response":[]},{"name":"MCP — end session (a no-op; every call is self-contained)","request":{"method":"DELETE","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/mcp","host":["{{baseUrl}}"],"path":["mcp"]},"description":"Requires any active key."},"response":[]}]},{"name":"Agent links","item":[{"name":"Ask one of your users to connect their iMatch account","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/agent-links/start","host":["{{baseUrl}}"],"path":["agent-links","start"]},"body":{"mode":"raw","raw":"{\n  \"external_user_id\": \"U-12\",\n  \"display_name\": \"Sara Khan\",\n  \"phone\": \"+971501234567\",\n  \"return_url\": \"https://crm.example.com/settings/imatch?connected=1\"\n}","options":{"raw":{"language":"json"}}},"description":"Call this from YOUR SERVER when the agent presses “Connect my iMatch account”. Store `link_id` against that seat and send the agent's BROWSER to `authorize_url`. They sign in to iMatch as themselves and agree, and we send them back to your `return_url` with `?link=ok&link_id=…` (or `?link=cancelled`). The token inside `authorize_url` is single-use, worth half an hour, and is never returned again — it is not a credential to keep. `email` is a hint for the consent screen: never stored, never matched against an iMatch account. `phone` is a hint AND a fence — still never stored (we keep a one-way fingerprint for the life of the request) and still able to find nobody, but an iMatch account signing in with a DIFFERENT verified WhatsApp number is refused, so a forwarded `authorize_url` cannot bind the wrong person to the seat. A TEST key gets a SANDBOX link that binds a synthetic agent — a sandbox key can never connect a real person.\n\nRequires any active key."},"response":[]},{"name":"List your agent links","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/agent-links","host":["{{baseUrl}}"],"path":["agent-links"],"query":[{"key":"page","value":"","description":"1-based page, default 1","disabled":true},{"key":"per_page","value":"","description":"1–100, default 25","disabled":true},{"key":"status","value":"","description":"Filter by status","disabled":true},{"key":"external_user_id","value":"","description":"Your own id for one CRM user","disabled":true}]},"description":"Yours only — the partner is taken from the key, never from the query. No phone or email is ever returned.\n\nRequires any active key."},"response":[]},{"name":"One agent link","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/agent-links/:id","host":["{{baseUrl}}"],"path":["agent-links",":id"],"variable":[{"key":"id","value":"","description":"Path parameter id"}]},"description":"Requires any active key."},"response":[]},{"name":"Disconnect an agent link","request":{"method":"DELETE","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/agent-links/:id","host":["{{baseUrl}}"],"path":["agent-links",":id"],"variable":[{"key":"id","value":"","description":"Path parameter id"}]},"description":"Ends the link on the agent's behalf (`revoked_by: partner`). Idempotent: a link that was already ended answers 200 with `meta.revoked: false`. The agent can also disconnect it in iMatch, their brokerage admin can revoke it, and deactivating their iMatch account revokes it automatically.\n\nRequires any active key."},"response":[]},{"name":"The linked agent's own listings","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/me/listings","host":["{{baseUrl}}"],"path":["me","listings"],"query":[{"key":"as_agent","value":"","description":"Act as the person behind this agent link, so ownership-aware reads return THEIR rows and the Copilot is theirs. The header `X-Connect-Agent` is accepted instead. A link id that is not yours, was revoked, whose iMatch account was deactivated, whose brokerage is no longer yours, or that is not a plain integer → 403 `agent_link_invalid`. A link the person granted narrower scopes than this call needs → 403 `agent_link_scope_denied`.","disabled":true},{"key":"page","value":"","description":"1-based page, default 1","disabled":true},{"key":"per_page","value":"","description":"1–100, default 25","disabled":true},{"key":"q","value":"","description":"Free text over reference, project, community, area, description","disabled":true},{"key":"purpose","value":"","description":"Sale | Rent","disabled":true},{"key":"property_type","value":"","disabled":true},{"key":"area","value":"","disabled":true},{"key":"community","value":"","disabled":true},{"key":"project","value":"","disabled":true},{"key":"developer","value":"","disabled":true},{"key":"bedrooms","value":"","description":"studio | 1 | 2 | … (exact); `5+` means five or more","disabled":true},{"key":"bathrooms","value":"","disabled":true},{"key":"min_price","value":"","disabled":true},{"key":"max_price","value":"","disabled":true},{"key":"min_sqft","value":"","disabled":true},{"key":"max_sqft","value":"","disabled":true},{"key":"furnished","value":"","disabled":true},{"key":"completion_status","value":"","description":"Ready | Off-Plan","disabled":true},{"key":"category","value":"","description":"Normal | Distress — only honoured with distress_read","disabled":true},{"key":"source","value":"","disabled":true},{"key":"updated_since","value":"","description":"ISO 8601 date or date-time, e.g. 2026-09-01T00:00:00Z","disabled":true},{"key":"created_since","value":"","description":"ISO 8601 date or date-time, e.g. 2026-09-01T00:00:00Z","disabled":true},{"key":"external_id","value":"","description":"Your own id (only your rows)","disabled":true},{"key":"ids","value":"","description":"Comma-separated iMatch ids (max 100)","disabled":true},{"key":"sort","value":"","description":"Prefix with - for descending. Default -updated_at","disabled":true},{"key":"include","value":"","description":"Comma-separated extras. `contact` adds agent.phone (charged as a reveal).","disabled":true}]},"description":"The same search as `/listings`, narrowed to the person named by `as_agent`. Requires the link: an endpoint that answers for ONE person must know which person, and it will never fall back to the brokerage. Ownership is the agent link on the row — a listing that merely quotes their phone number is not theirs. Unlike `/listings`, this returns their rows in ANY state, including the ones they have NOT published (Unlisted, Sold) — that is what the consent screen promises them. Read `live_status` on each row to filter. Under a TEST key this returns the rows that key itself sent.\n\nRequires the `listings_read` feature switch."},"response":[]},{"name":"The linked agent's own inquiries","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/me/inquiries","host":["{{baseUrl}}"],"path":["me","inquiries"],"query":[{"key":"as_agent","value":"","description":"Act as the person behind this agent link, so ownership-aware reads return THEIR rows and the Copilot is theirs. The header `X-Connect-Agent` is accepted instead. A link id that is not yours, was revoked, whose iMatch account was deactivated, whose brokerage is no longer yours, or that is not a plain integer → 403 `agent_link_invalid`. A link the person granted narrower scopes than this call needs → 403 `agent_link_scope_denied`.","disabled":true},{"key":"page","value":"","description":"1-based page, default 1","disabled":true},{"key":"per_page","value":"","description":"1–100, default 25","disabled":true},{"key":"q","value":"","description":"Free text over reference, location, project, requirements","disabled":true},{"key":"purpose","value":"","disabled":true},{"key":"location","value":"","description":"Matches location_wanted / preferred_communities","disabled":true},{"key":"project","value":"","disabled":true},{"key":"property_type","value":"","disabled":true},{"key":"bedrooms","value":"","disabled":true},{"key":"min_budget","value":"","disabled":true},{"key":"max_budget","value":"","disabled":true},{"key":"updated_since","value":"","description":"ISO 8601 date or date-time, e.g. 2026-09-01T00:00:00Z","disabled":true},{"key":"created_since","value":"","description":"ISO 8601 date or date-time, e.g. 2026-09-01T00:00:00Z","disabled":true},{"key":"source","value":"","disabled":true},{"key":"external_id","value":"","disabled":true},{"key":"ids","value":"","description":"Comma-separated iMatch ids (max 100)","disabled":true},{"key":"sort","value":"","disabled":true},{"key":"include","value":"","description":"Comma-separated extras. `contact` adds agent.phone (charged as a reveal).","disabled":true}]},"description":"The same search as `/inquiries`, narrowed to the person named by `as_agent`. Requires the link. Returns their inquiries in ANY state, including closed ones — read `live_status` on each row to filter.\n\nRequires the `inquiries_read` feature switch."},"response":[]},{"name":"The linked agent's own matches","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":{"raw":"{{baseUrl}}/me/matches","host":["{{baseUrl}}"],"path":["me","matches"],"query":[{"key":"as_agent","value":"","description":"Act as the person behind this agent link, so ownership-aware reads return THEIR rows and the Copilot is theirs. The header `X-Connect-Agent` is accepted instead. A link id that is not yours, was revoked, whose iMatch account was deactivated, whose brokerage is no longer yours, or that is not a plain integer → 403 `agent_link_invalid`. A link the person granted narrower scopes than this call needs → 403 `agent_link_scope_denied`.","disabled":true},{"key":"page","value":"","description":"1-based page, default 1","disabled":true},{"key":"per_page","value":"","description":"1–100, default 25","disabled":true},{"key":"since","value":"","description":"Only matches created at/after this instant","disabled":true},{"key":"status","value":"","disabled":true},{"key":"side","value":"","description":"Which side of the match is yours","disabled":true},{"key":"listing_id","value":"","disabled":true},{"key":"inquiry_id","value":"","disabled":true},{"key":"min_score","value":"","disabled":true},{"key":"include","value":"","description":"Comma-separated extras. `contact` adds the other party's agent.phone (charged as a reveal). `nearby` adds NEARBY matches (the right area, not the exact building) — left out unless you ask; each match says which it is in `kind`.","disabled":true}]},"description":"Matches where EITHER side is theirs. The other party's contact is still a metered reveal (`include=contact`); their own side's number is free — it is their own. Requires the link. Exact matches only unless you send `include=nearby`, as on `GET /matches`.\n\nRequires the `matches_read` feature switch."},"response":[]}]},{"name":"Sandbox","item":[{"name":"Synthesise a webhook event (test keys only)","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/sandbox/events","host":["{{baseUrl}}"],"path":["sandbox","events"]},"body":{"mode":"raw","raw":"{\n  \"event\": \"match.created\",\n  \"sample\": {\n    \"match\": {\n      \"score\": 92\n    }\n  }\n}","options":{"raw":{"language":"json"}}},"description":"Builds a realistic payload for `event` — from your own sandbox rows when you have any, else a canned sample — and delivers it to your TEST webhooks exactly like a live event: same envelope, same signature, same worker, plus `X-Connect-Mode: test`. `sample` is deep-merged over the generated `data` so you can shape it (≤ 16 KB). A live key answers 400 `live_key`. The event's feature gate is the live one (`match.created` needs `matches_read`, `distress.created` needs `distress_read`).\n\nRequires the `webhooks` feature switch."},"response":[]}]}]}