API Users

The Markondo API

A read API over our new-development catalogue. Pull listings, pricing and images into your own site or feed; every listing carries a link back to markondo.com, where the enquiry reaches the developer.

It is a syndication API, not a data feed. You get what a visitor sees on a listing page — never the developer's contact details.

Getting a key

Sign in and ask on your API access page , telling us what you plan to build. A person reads it — the API returns the whole catalogue, so we like to know who is pulling it.

Once you are approved, you create the key yourself on that page and it is shown once, in your browser. It is never emailed and nobody here can read it back to you.

A key is shown once, when it is created, and only its hash is stored — we cannot recover it for you. If you lose one, ask for a replacement and the old one is revoked.

Keys are per partner, so one can be switched off without affecting anyone else.

Making a request

Every request carries the key as a bearer token.

curl https://markondo.com/api/v1/projects \
  -H "Authorization: Bearer YOUR_KEY"

A missing, malformed or revoked key returns 401 with {"error": "unauthorized"}. The three cases are deliberately indistinguishable.

Endpoints

GET /api/v1/projects

A page of the catalogue, newest changes first.

limit integer How many listings to return. Defaults to 50.
cursor string The next_cursor from your previous response. Omit for the first page.
updated_since ISO 8601 Only listings changed since this time — how you keep a copy in step without re-crawling.
city string Filter by city, exact match.
country string Filter by country, exact match.
status string planning, under_construction or completed.
GET /api/v1/projects/:id

One listing, by slug or by id. Prefer the slug — it is stable and readable.

A listing that is hidden or unpublished returns 404, exactly as one that never existed. Telling the two apart would say what is in the catalogue that you cannot see.

Paging through the catalogue

Responses carry a next_cursor. Pass it back as cursor for the following page, and stop when it is null — that means you have reached the end, not that something went wrong.

The cursor is opaque: it encodes a position, not a page number, so listings added mid-crawl cannot make you skip or repeat one. Treat it as a string and do not construct your own. A cursor we cannot read starts you from the beginning rather than erroring.

GET /api/v1/projects?limit=100
GET /api/v1/projects?limit=100&cursor=MjAyNi0wOC0yOFQxMTo0MDowMFp8YWJjMTIz

What a listing looks like

{
  "data": [
    {
      "id": "a30d53a8-f530-…",
      "name": "Rivage Bal Harbour",
      "slug": "rivage-bal-harbour",
      "url": "https://markondo.com/projects/rivage-bal-harbour",
      "description": "At Home in the Sky…",
      "location": {
        "address": "10245 Collins Ave",
        "city": "Bal Harbour",
        "state": "FL",
        "country": "United States",
        "neighborhoods": [],
        "latitude": 25.8896,
        "longitude": -80.1256
      },
      "building": {
        "property_type": "condo",
        "status": "under_construction",
        "year_built": 2027,
        "total_units": 56,
        "stories": 25,
        "architect": "…",
        "interior_designer": "…",
        "amenities": ["Pool deck", "Spa"],
        "highlights": ["Oceanfront"]
      },
      "pricing": {
        "price_from": 4200000,
        "currency": "USD",
        "min_ticket": null,
        "unit_mix": "3–5 bed",
        "payment_plan": "20% on contract…"
      },
      "developer": { "name": "Related Group" },
      "images": ["https://…"],
      "updated_at": "2026-08-27T19:12:53Z"
    }
  ],
  "next_cursor": "MjAyNi0wOC0yN1QxOToxMjo1M1p8YTMwZDUzYTg"
}

url is included on every listing deliberately. Linking back is the deal: you get the content, the developer gets the enquiry.

What the API never returns

Worth stating plainly, because it is the question every developer listing with us asks. These fields exist on our side and are not serialized at any endpoint, at any access level:

  • Developer contact details — Email, phone, sales team and notification addresses. This is the developer's asset, not ours to distribute.
  • Who holds the listing on Markondo — Owner and creator accounts stay internal.
  • Internal state — Review status, hidden flags, sample listings, our quality scores and lifecycle stage. How the sausage is made is not part of the catalogue.
  • CRM and import identifiers — HubSpot ids and legacy import keys are joins into our own systems.

The serializer allowlists fields rather than excluding them, so a column added to a listing later cannot appear here by accident — someone has to decide to publish it.

Rate limit

600 requests per minute, per key. It is a ceiling on runaway crawls rather than a pace for normal use — syncing hourly, or walking the whole catalogue several times a day, will never reach it.

Over the limit returns 429 with a Retry-After header in seconds. The body names the limit and the window, so a client can back off correctly without asking us what the numbers are.

If you have a use that genuinely needs more, say so — the limit is per key and we can raise yours without affecting anyone else.

Errors

401 unauthorized The key is missing, malformed, revoked, or belongs to a closed account.
404 not found No listing with that slug or id is publicly visible.
429 rate_limited Over the per-key limit. Wait the number of seconds in Retry-After.

Using it fairly

Cache what you pull and use updated_since rather than re-crawling the whole catalogue — it is the difference between one request and several hundred, it keeps your copy fresher, and it keeps you far below the rate limit without thinking about it.

Keep the url on anything you publish, and keep the developer's name with their listing.

If you need something the API does not return, ask rather than working around it. Some of it we will add; the contact details we will not.

Questions, or something missing from the payload? hello@markondo.com.