OffplanIndex
Prices on this site were last read 30 July 2026, 64 days ago.New launches added up to 30 September 2026.
API · sources read to 2026-07-30 · build 2026-10-02

The OffplanIndex API

A REST read API over the same corpus this site is built from: 2,962 off-plan projects, 231 communities and 681 developers across the UAE, priced from 52,882 unit records. It is built for two audiences: portals that want price per sqft and payment plan benchmarks they do not have to compute themselves, and agencies that want to pull project and developer data into their own tools.

The licence is short: everything served here is OffplanIndex's own derived analytics, aggregated and computed from developer published facts and publicly observed listings, never a copy of a portal's raw database and never an image URL pointing back at one. Full terms are on the data policy page, and a key is sold on the data licence page.

Endpoints
MethodPathWhat it returns
GET/api/v1/projectsFilterable list of every project
GET/api/v1/projects/{id}One project, with its psf position
GET/api/v1/projects/{id}/historyEvery dated read of one project: handover, status, price per read
GET/api/v1/changesWhat has been restated since a date: handover, status and price revisions, newest first
GET/api/v1/communitiesEvery tracked community
GET/api/v1/communities/{slug}One community, full aggregate
GET/api/v1/developersEvery tracked developer
GET/api/v1/developers/{slug}One developer, full aggregate
GET/api/v1/marketDubai and UAE aggregates
GET/api/v1/syncCursor delta feed for downstream consumers, HMAC signed, not x-api-key
GET/api/v1/metaCorpus counts and build date, unkeyed
GET/api/v1/qualityEvery quality gate between a corpus record and a published number, with counts, unkeyed
GET/api/v1/usageYour key's consumer, tier and remaining quota, keyed, never counted
GET/api/v1/openapi.jsonOpenAPI 3.1 description of every route above, generated from the same builders, unkeyed
Auth

Header, errors, rate limit

Every endpoint except /api/v1/meta and /api/v1/sync requires a key sent as the x-api-key header. Get one on the data licence page. A missing key and an unrecognised key return the same 401 body, so a bad key cannot be told apart from no key at all. /sync takes a different, HMAC scheme instead, see HMAC for /sync below; an x-api-key on that route is ignored, not accepted as a fallback.

Every response, success or error, carries meta.schema_version, currently 1. It only moves on a breaking change to a response shape. A breaking change ships as a new endpoint under /api/v2, never as a silent reshape of something under /api/v1, and /api/v1 keeps serving for at least one quarter after /api/v2 ships. Refuse a schema_version you do not recognise rather than guessing at field presence. Beside it, meta.dataset_version (YYYY-MM of the build) names the data edition, the same value the licence bundle carries, so a consumer can tell a schema change from a data refresh.

Each key sits on a tier, and the tier sets a per minute rate limit and a daily quota. Starter, 30 a minute and 1,000 a day. Team, 60 and 10,000. Data licence, 120 and 100,000. A licence can be issued with its own numbers. Each keyed response carries x-ratelimit-limit, x-ratelimit-remaining, x-quota-limit-day and x-quota-remaining-day; a 429 adds x-quota-exceeded (minute or day) and Retry-After. Call /api/v1/usage with your key to read your own usage without spending a call.

/api/v1/changes answers the question a monthly licence exists for: what has been restated since you last pulled. Every tracked field whose value differs between two dated reads of the same project, newest first, filterable by since and by field, with both read dates on every row. It fills from the first refresh that brings a newer read of a project we already hold. Every row says whether its two reads came from the same source: cross_source is true for all of them today, because the corpus has been read once and the second read came from another portal. One source restating itself is stronger evidence, and those appear once the watcher runs again. The two sources also name the same states differently, so revisions are compared by meaning rather than by word.

A community or developer with ten or more projects priced on both reads carries price_movement: how many quoted a higher starting price on the second read, how many lower, and the median move. Read it as inventory turning over rather than as a price index, which is what its own caveat says: a starting price is the cheapest unit on offer, so a lower figure usually means a cheaper unit came to market and a higher one means the cheapest sold.

A developer with ten or more projects read twice carries handover_revisions on its detail response: how many of its stated handover dates read later on the second read, how many read earlier, the sample size and the median slip. It travels with its own caveat because it is the number most likely to be quoted out of context. It counts what two portals said three weeks apart, not deliveries.

Argue with the numbers: /api/v1/quality is public and lists every gate between a record arriving in the corpus and a figure appearing here, with how many records each one removed, the flags we carry, and what this dataset cannot tell you.

One thing to store beside your cursor on /sync: data.dataset_version. A record's updated_at is the day we read it, not the day we rebuilt, which is what keeps the cursor stable across a rebuild. The consequence is that when we add a field, records you already hold keep their read date and a cursor walk will not hand them back. If dataset_version has moved and you want the new fields on old records, walk once from the beginning.

History: /api/v1/projects/{id}/history returns every dated read the ledger holds for a project, oldest first, with the source and what each read stated (handover, status, price, price per sqft, plan discount). Two portals can disagree, so the read dates travel with every row; a handover that reads later on a later read is evidence of a revision, a lower starting price usually means a cheaper unit came to market.

Machine readable: /api/v1/openapi.json is an OpenAPI 3.1 document generated from the same code that serves these routes, so it cannot drift from them. It carries inferred response schemas and live examples, enough for an agent or a client generator to call the API without reading this page.

Key your own store on opi_id, not on id. id is the site slug, a URL, and a URL can change when two records turn out to be one building, or when we rename a slug (every rename is recorded and redirected, and the previous id still resolves on the API). opi_id is derived from the developer and the project title. It does not change when a URL changes. Both are accepted on /api/v1/projects/{id}, and every sync record and tombstone carries both.

Every keyed response carries Cache-Control: private, no-store and Vary: x-api-key. That is deliberate: a shared cache in front of this API keys on the URL alone and knows nothing about your key, so a public directive would let it hand your payload to the next caller who asks for the same path. Cache in your own process instead, keyed on X-Corpus-Version, which every response carries and which only changes when the corpus is rebuilt. The one exception is /meta, which needs no key and carries no records, so it is cacheable for an hour.

ParamTypeNotes
x-api-keyheader, stringRequired on every endpoint except /api/v1/meta.
Statuserror.codeMeaning
400bad_requestA query parameter was malformed, for example a non numeric price bound.
401unauthorizedx-api-key is missing or not on the allowlist (same body either way, on purpose). On /sync specifically: a missing HMAC header, an unknown key id, a timestamp more than 300s from server time, or a signature that does not match.
404not_foundNo project, community or developer exists with that id or slug.
429rate_limitedThis key went over its minute rate limit or its daily quota; x-quota-exceeded names which. Check the Retry-After header. Every keyed response carries x-ratelimit-limit, x-ratelimit-remaining, x-quota-limit-day and x-quota-remaining-day.

Every error body has the shape { error: { code, message } }.

Each key is limited to 600 requests per minute. Going over it returns 429 with a Retry-After header in seconds. The limit is tracked in the memory of the server instance handling the request, not in a shared store, so it protects a single instance rather than being a hard global ceiling across every instance behind the API. Treat 600 per minute per key as the number to design around, not a guarantee down to the request.

Freshness

What X-Corpus-Version and updated_at mean, honestly

The corpus is rebuilt periodically from source rather than streamed continuously. X-Corpus-Version is on every response, set to the build date, so you can tell when the whole corpus last moved, but a build date alone would make it look like every one of 2,962 off-plan projects changed on every rebuild, which is not true and would make a freshness signal useless.

So /api/v1/sync stamps each record with its own updated_at instead: pipeline/build_db.py hashes exactly what this API publishes about a project and carries that hash between builds, in pipeline/lastmod-ledger.json. An unchanged hash keeps its existing updated_at forever, no matter how many times the pipeline reruns; a changed hash takes the date the record was actually re-read, from its own scrape timestamp, never a date later than the day we can prove we read it. The same ledger backs the sitemap's lastmod, so a page's crawl freshness and its API sync freshness are the same fact, not two. content_hash travels alongside updated_at on every sync record if you would rather diff a hash yourself than trust the date.

Endpoint

GET /api/v1/projects

Filterable list of every tracked project.

ParamTypeNotes
citystringExact match, case insensitive, for example Dubai.
communitystringCommunity slug, for example business-bay.
developerstringDeveloper slug, for example danube.
statusstringSale status: on_sale, out_of_stock, presale, sold_out, announced, start_of_sales, booking_started, waiting_for_sales_start, might_be_sold_out.
handover_yearintegerExact match on the scheduled handover year.
min_pricenumberAED, inclusive. Rows with no stated starting price are excluded once this is set.
max_pricenumberAED, inclusive. Same exclusion rule as min_price.
bedroomsstringA single value, for example 2 or 0 for studio. Matches projects whose bedroom mix includes it.
qstringCase insensitive substring match on the project title.
limitintegerDefault 50, maximum 500.
cursorstringOpaque, from meta.next_cursor on the previous page.
curl -s "https://offplanindex.com/api/v1/projects?city=Dubai&limit=2" \
  -H "x-api-key: YOUR_KEY"
{
  "data": [
    {
      "id": "durar-j-one-tower-a",
      "opi_id": "opi-5ec19f55df",
      "title": "J One Tower A",
      "developer": "Durar",
      "developer_slug": "durar",
      "community": "Business Bay",
      "community_slug": "business-bay",
      "city": "Dubai",
      "starting_price": null,
      "delivery_quarter": "Q4 2021",
      "delivery_year": 2021,
      "sale_status": "out_of_stock",
      "construction_status": "completed",
      "property_types": null,
      "bedrooms": null,
      "n_units": 0,
      "n_plans": 0,
      "lat": 25.186162564692015,
      "lng": 55.28328666778401
    },
    {
      "id": "sol-properties-sol-bay",
      "opi_id": "opi-46a17064c8",
      "title": "SOL Bay",
      "developer": "Sol Properties",
      "developer_slug": "sol-properties",
      "community": "Business Bay",
      "community_slug": "business-bay",
      "city": "Dubai",
      "starting_price": 2040000,
      "delivery_quarter": "Q4 2020",
      "delivery_year": 2020,
      "sale_status": "on_sale",
      "construction_status": "completed",
      "property_types": null,
      "bedrooms": "1",
      "n_units": 9,
      "n_plans": 1,
      "lat": 25.18034209709853,
      "lng": 55.273135884655545
    }
  ],
  "meta": {
    "total": 3234,
    "limit": 2,
    "cursor": "MA",
    "next_cursor": "Mg",
    "license": "Derived analytics only: aggregates and analysis computed by OffplanIndex from developer published project facts and publicly observed listings. Not a redistribution of any portal's database, and no image URLs are served. See /data-policy for the licence and /data to buy a key."
  }
}

Live sample, filtered to city=Dubai and limit=2. meta.total counts the full filtered set, not just this page.

Endpoint

GET /api/v1/projects/{id}

One project's full record, resolved from its community's data and, if that is missing, from its developer's data. Includes an estimated price per sqft position against its community and against the Dubai market.

ParamTypeNotes
idstring, pathProject id, as returned by /api/v1/projects.
curl -s "https://offplanindex.com/api/v1/projects/danube-properties-bayz-102" \
  -H "x-api-key: YOUR_KEY"
{
  "id": "danube-properties-bayz-102",
  "opi_id": "opi-c8c71f2587",
  "title": "Bayz 102",
  "developer": "Danube",
  "developer_slug": "danube",
  "community": "Business Bay",
  "community_slug": "business-bay",
  "city": "Dubai",
  "starting_price": 2525000,
  "delivery_quarter": "Q4 2029",
  "delivery_year": 2029,
  "sale_status": "on_sale",
  "construction_status": "under_construction",
  "property_types": "apartment,penthouse",
  "bedrooms": "1,2,3,4,5",
  "n_units": 299,
  "n_plans": 9,
  "lat": 25.187670501380495,
  "lng": 55.25988699826104,
  "psf_median": 2944,
  "pre_handover_pct": 50,
  "post_handover_pct": 50,
  "psf_position": {
    "community": {
      "percentile_estimate": 39,
      "comparator_median_psf": 3188,
      "delta_vs_median_pct": -7.7,
      "note": "percentile_estimate is a linear interpolation between the comparator's published quantiles (min, p25, median, p75, max), not a rank over the raw distribution."
    },
    "dubai_market": {
      "percentile_estimate": 75,
      "comparator_median_psf": 2021,
      "delta_vs_median_pct": 45.7,
      "note": "percentile_estimate is a linear interpolation between the comparator's published quantiles (min, p25, median, p75, max), not a rank over the raw distribution."
    }
  }
}

psf_position.percentile_estimate is interpolated between the comparator's published min, p25, median, p75 and max, it is not computed over the raw unit level distribution. Either side is null when the project has no priced units or the comparator has no psf statistics.

Endpoint

GET /api/v1/communities

Every tracked community, one summary row each.

ParamTypeNotes
citystringExact match, case insensitive.
qstringCase insensitive substring match on the community name.
limitintegerDefault 100, maximum 500.
cursorstringOpaque, from meta.next_cursor.
curl -s "https://offplanindex.com/api/v1/communities?limit=2" \
  -H "x-api-key: YOUR_KEY"
{
  "data": [
    {
      "name": "Dubai Islands",
      "slug": "dubai-islands",
      "city": "Dubai",
      "n_projects": 187,
      "n_units": 2309,
      "psf_median": 2492,
      "psf_p25": 2139,
      "psf_p75": 2962,
      "price_median": 3407000,
      "starting_price_min": 1100000,
      "lat": 25.29912157311391,
      "lng": 55.30294552,
      "rank_psf_dubai": 26,
      "pre_handover_median": 50,
      "n_developers": 87
    },
    {
      "name": "Jumeirah Village Circle",
      "slug": "jumeirah-village-circle",
      "city": "Dubai",
      "n_projects": 169,
      "n_units": 2808,
      "psf_median": 1654,
      "psf_p25": 1462,
      "psf_p75": 1950,
      "price_median": 1308220,
      "starting_price_min": 407000,
      "lat": 25.057261108587365,
      "lng": 55.20858446715988,
      "rank_psf_dubai": 53,
      "pre_handover_median": 60,
      "n_developers": 141
    }
  ],
  "meta": {
    "total": 231,
    "limit": 2,
    "cursor": "MA",
    "next_cursor": "Mg",
    "license": "Derived analytics only: aggregates and analysis computed by OffplanIndex from developer published project facts and publicly observed listings. Not a redistribution of any portal's database, and no image URLs are served. See /data-policy for the licence and /data to buy a key."
  }
}
Endpoint

GET /api/v1/communities/{slug}

One community's full aggregate: price and size distributions, bedroom and property type breakdowns, the delivery pipeline, payment plan norms and its own project list.

ParamTypeNotes
slugstring, pathCommunity slug, as returned by /api/v1/communities.
curl -s "https://offplanindex.com/api/v1/communities/business-bay" \
  -H "x-api-key: YOUR_KEY"
{
  "name": "Business Bay",
  "slug": "business-bay",
  "kind": "community",
  "city": "Dubai",
  "lat": 25.184333054495546,
  "lng": 55.27318136402,
  "n_projects": 68,
  "n_records": 134,
  "n_delivered_projects": 66,
  "n_priced_projects": 62,
  "n_projects_with_units": 94,
  "n_units": 1458,
  "n_units_unit_level": 1287,
  "n_projects_with_plans": 104,
  "psf": {
    "n": 1458,
    "min": 474,
    "p25": 2650,
    "median": 3188,
    "p75": 3664,
    "max": 17223,
    "mean": 3451
  },
  "psf_hist": {
    "lo": 2110,
    "hi": 7557,
    "step": 226.98,
    "counts": [
      17,
      171,
      235,
      164,
      169,
      171
    ]
  },
  "h": [
    0,
    1,
    1,
    4,
    11,
    124,
    954,
    275,
    63,
    21,
    3,
    1
  ],
  "price": {
    "n": 1458,
    "min": 650000,
    "p25": 2618250,
    "median": 3612017,
    "p75": 5672046,
    "max": 175000000,
    "mean": 7351024
  },
  "size": {
    "n": 1458,
    "min": 344,
    "p25": 851,
    "median": 1209,
    "p75": 1866,
    "max": 14632,
    "mean": 1764
  },
  "starting_price": {
    "n": 111,
    "min": 650000,
    "p25": 1255000,
    "median": 2207000,
    "p75": 3713914,
    "max": 85000000,
    "mean": 6707673
  },
  "by_bedroom": [
    {
      "bedrooms": "Studio",
      "n": 63,
      "psf": {
        "n": 63,
        "min": 1890,
        "p25": 3062,
        "median": 3531,
        "p75": 3664,
        "max": 6138,
        "mean": 3477
      },
      "price": {
        "n": 63,
        "min": 650000,
        "p25": 1460777,
        "median": 1713777,
        "p75": 1929277,
        "max": 2750000,
        "mean": 1693562
      },
      "size": {
        "n": 63,
        "min": 344,
        "p25": 399,
        "median": 485,
        "p75": 542,
        "max": 741,
        "mean": 502
      }
    },
    {
      "bedrooms": "1 BR",
      "n": 468,
      "psf": {
        "n": 468,
        "min": 474,
        "p25": 2549,
        "median": 2864,
        "p75": 3454,
        "max": 4697,
        "mean": 2978
      },
      "price": {
        "n": 468,
        "min": 1000000,
        "p25": 2150000,
        "median": 2544999,
        "p75": 2778000,
        "max": 4485634,
        "mean": 2543350
      },
      "size": {
        "n": 468,
        "min": 608,
        "p25": 772,
        "median": 826,
        "p75": 975,
        "max": 2112,
        "mean": 867
      }
    },
    {
      "bedrooms": "2 BR",
      "n": 609,
      "psf": {
        "n": 609,
        "min": 786,
        "p25": 2714,
        "median": 2969,
        "p75": 3308,
        "max": 10231,
        "mean": 3156
      },
      "price": {
        "n": 609,
        "min": 1599999,
        "p25": 3482000,
        "median": 3760000,
        "p75": 4700000,
        "max": 31600000,
        "mean": 4900664
      },
      "size": {
        "n": 609,
        "min": 818,
        "p25": 1199,
        "median": 1321,
        "p75": 1481,
        "max": 4544,
        "mean": 1497
      }
    }
  ],
  "by_type": [
    {
      "type": "apartment",
      "n": 1364,
      "psf_median": 3162,
      "psf": {
        "n": 1364,
        "min": 474,
        "p25": 2639,
        "median": 3162,
        "p75": 3564,
        "max": 17223,
        "mean": 3394
      },
      "price": {
        "n": 1364,
        "min": 650000,
        "p25": 2583250,
        "median": 3515000,
        "p75": 4817170,
        "max": 175000000,
        "mean": 6308579
      },
      "size": {
        "n": 1364,
        "min": 344,
        "p25": 835,
        "median": 1199,
        "p75": 1564,
        "max": 12853,
        "mean": 1573
      }
    },
    {
      "type": "duplex",
      "n": 60,
      "psf_median": 3850,
      "psf": {
        "n": 60,
        "min": 923,
        "p25": 2637,
        "median": 3850,
        "p75": 4271,
        "max": 8399,
        "mean": 3865
      },
      "price": {
        "n": 60,
        "min": 3403000,
        "p25": 6627902,
        "median": 10896031,
        "p75": 23000000,
        "max": 80012051,
        "mean": 17606679
      },
      "size": {
        "n": 60,
        "min": 1713,
        "p25": 2283,
        "median": 3314,
        "p75": 5389,
        "max": 10280,
        "mean": 3947
      }
    },
    {
      "type": "penthouse",
      "n": 32,
      "psf_median": 3680,
      "psf": {
        "n": 32,
        "min": 2731,
        "p25": 3166,
        "median": 3680,
        "p75": 5547,
        "max": 16391,
        "mean": 4850
      },
      "price": {
        "n": 32,
        "min": 3702000,
        "p25": 17387016,
        "median": 19283344,
        "p75": 33782210,
        "max": 165000000,
        "mean": 30676061
      },
      "size": {
        "n": 32,
        "min": 1199,
        "p25": 4090,
        "median": 5286,
        "p75": 6186,
        "max": 14632,
        "mean": 5689
      }
    }
  ],
  "pipeline_year": [
    {
      "year": 2020,
      "projects": 3
    },
    {
      "year": 2021,
      "projects": 2
    },
    {
      "year": 2022,
      "projects": 2
    },
    {
      "year": 2023,
      "projects": 15
    }
  ],
  "pipeline_quarter": [
    {
      "quarter": "Q1 2024",
      "projects": 2
    },
    {
      "quarter": "Q2 2024",
      "projects": 4
    },
    {
      "quarter": "Q3 2024",
      "projects": 1
    },
    {
      "quarter": "Q4 2024",
      "projects": 9
    }
  ],
  "sale_status": {
    "on_sale": 40,
    "out_of_stock": 29,
    "sold_out": 20,
    "unknown": 20,
    "booking_started": 12,
    "available": 9,
    "might_be_sold_out": 3,
    "waiting_for_sales_start": 1
  },
  "construction_status": {
    "completed": 57,
    "under_construction": 55,
    "unknown": 20,
    "not_started": 2
  },
  "property_types": {
    "apartment": 79,
    "penthouse": 22,
    "duplex": 17,
    "villa": 3,
    "townhouse": 2
  },
  "payment_plans": {
    "n": 124,
    "booking": {
      "n": 124,
      "min": 0,
      "p25": 10,
      "median": 20,
      "p75": 32,
      "max": 100,
      "mean": 33
    },
    "construction": {
      "n": 124,
      "min": 0,
      "p25": 0,
      "median": 35,
      "p75": 50,
      "max": 80,
      "mean": 30
    },
    "handover": {
      "n": 124,
      "min": 0,
      "p25": 0,
      "median": 30,
      "p75": 50,
      "max": 80,
      "mean": 27
    },
    "post_handover": {
      "n": 25,
      "min": 10,
      "p25": 32,
      "median": 40,
      "p75": 60,
      "max": 90,
      "mean": 48
    },
    "pre_handover": {
      "n": 124,
      "min": 10,
      "p25": 40,
      "median": 60,
      "p75": 80,
      "max": 100,
      "mean": 63
    },
    "share_post_handover": 20,
    "share_pre_handover_60_plus": 59,
    "share_pre_handover_50_or_less": 40
  },
  "down_payment": {
    "n": 66,
    "min": 5,
    "p25": 10,
    "median": 10,
    "p75": 20,
    "max": 50,
    "mean": 14
  },
  "cities": {
    "Dubai": 134
  },
  "n_developers": 50,
  "top_developers": [
    {
      "name": "DAMAC",
      "slug": "damac",
      "projects": 20
    },
    {
      "name": "Select Group",
      "slug": "select-group",
      "projects": 15
    },
    {
      "name": "Omniyat",
      "slug": "omniyat",
      "projects": 9
    }
  ],
  "projects": [
    {
      "id": "danube-properties-bayz-102",
      "opi_id": "opi-c8c71f2587",
      "title": "Bayz 102",
      "developer": "Danube",
      "developer_slug": "danube",
      "starting_price": 2525000,
      "delivery_quarter": "Q4 2029",
      "delivery_year": 2029,
      "sale_status": "on_sale",
      "construction_status": "under_construction",
      "delivery_status": "off_plan",
      "property_types": "apartment,penthouse",
      "n_units": 299,
      "psf_median": 2944,
      "lat": 25.187670501380495,
      "lng": 55.25988699826104,
      "pre_handover_pct": 50,
      "post_handover_pct": 50
    },
    {
      "id": "danube-properties-bayz-101",
      "opi_id": "opi-e69d691941",
      "title": "Bayz 101",
      "developer": "Danube",
      "developer_slug": "danube",
      "starting_price": 1407000,
      "delivery_quarter": "Q2 2029",
      "delivery_year": 2029,
      "sale_status": "on_sale",
      "construction_status": "under_construction",
      "delivery_status": "off_plan",
      "property_types": "apartment",
      "n_units": 146,
      "psf_median": 3432,
      "lat": 25.191826890074058,
      "lng": 55.26334006786653,
      "pre_handover_pct": 50,
      "post_handover_pct": 50
    }
  ],
  "rank_psf_dubai": 12,
  "price_movement": {
    "slug": "business-bay",
    "name": "Business Bay",
    "n_both_priced": 72,
    "up": 5,
    "down": 10,
    "unchanged": 57,
    "median_move_pct": -8.8,
    "method": "Starting price on the same project in two reads three weeks apart, matched on title plus developer. A move counts above 1 percent either way; smaller differences are rounding or a portal formatting choice.",
    "caveat": "Asking prices from two different portals, never transactions. A starting price is the cheapest unit on offer that day, so a lower figure on the second read usually means a cheaper unit came to market, not that the project was repriced; a higher one usually means the cheapest units sold. Lines need at least ten priced projects.",
    "not_measured": "This is not a price index and not a measure of value. It counts projects whose quoted starting price differed between two reads three weeks apart, with the direction. Use it as a liveness signal (which communities are moving inventory) and quote the count of projects behind every line."
  }
}

Trimmed for this page only: projects, by_bedroom, by_type, pipeline_year, pipeline_quarter, top_developers and psf_hist.counts are sliced short. The live response does not trim these.

Endpoint

GET /api/v1/developers

Every tracked developer, one summary row each.

ParamTypeNotes
qstringCase insensitive substring match on the developer name.
limitintegerDefault 100, maximum 500.
cursorstringOpaque, from meta.next_cursor.
curl -s "https://offplanindex.com/api/v1/developers?limit=2" \
  -H "x-api-key: YOUR_KEY"
{
  "data": [
    {
      "name": "Emaar",
      "slug": "emaar",
      "n_projects": 229,
      "n_units": 1712,
      "psf_median": 2068,
      "psf_p25": 1624,
      "psf_p75": 2485,
      "price_median": 3193388,
      "starting_price_min": 660000,
      "rank_psf": 68,
      "rank_projects": 1,
      "pre_handover_median": 80,
      "n_communities": 28,
      "top_communities": [
        {
          "name": "Dubai Creek Harbour",
          "slug": "dubai-creek-harbour",
          "projects": 53
        },
        {
          "name": "The Valley",
          "slug": "the-valley",
          "projects": 49
        },
        {
          "name": "Dubai Hills Estate",
          "slug": "dubai-hills-estate",
          "projects": 46
        }
      ]
    },
    {
      "name": "DAMAC",
      "slug": "damac",
      "n_projects": 173,
      "n_units": 1812,
      "psf_median": 1580,
      "psf_p25": 1398,
      "psf_p75": 1903,
      "price_median": 2669000,
      "starting_price_min": 535500,
      "rank_psf": 128,
      "rank_projects": 2,
      "pre_handover_median": 75,
      "n_communities": 23,
      "top_communities": [
        {
          "name": "DAMAC Lagoons",
          "slug": "damac-lagoons",
          "projects": 45
        },
        {
          "name": "Dubai Investments Park",
          "slug": "dubai-investments-park",
          "projects": 39
        },
        {
          "name": "DAMAC Hills",
          "slug": "damac-hills",
          "projects": 37
        }
      ]
    }
  ],
  "meta": {
    "total": 681,
    "limit": 2,
    "cursor": "MA",
    "next_cursor": "Mg",
    "license": "Derived analytics only: aggregates and analysis computed by OffplanIndex from developer published project facts and publicly observed listings. Not a redistribution of any portal's database, and no image URLs are served. See /data-policy for the licence and /data to buy a key."
  }
}
Endpoint

GET /api/v1/developers/{slug}

One developer's full aggregate, the same shape as a community aggregate, plus its community footprint.

ParamTypeNotes
slugstring, pathDeveloper slug, as returned by /api/v1/developers.
curl -s "https://offplanindex.com/api/v1/developers/danube" \
  -H "x-api-key: YOUR_KEY"
{
  "name": "Danube",
  "slug": "danube",
  "kind": "developer",
  "n_projects": 22,
  "n_records": 31,
  "n_delivered_projects": 9,
  "n_priced_projects": 21,
  "n_projects_with_units": 29,
  "n_units": 1370,
  "n_units_unit_level": 1275,
  "n_projects_with_plans": 29,
  "psf": {
    "n": 1370,
    "min": 829,
    "p25": 2175,
    "median": 2549,
    "p75": 3336,
    "max": 5063,
    "mean": 2703
  },
  "psf_hist": {
    "lo": 1415,
    "hi": 4627,
    "step": 133.86,
    "counts": [
      60,
      77,
      61,
      53,
      20,
      88
    ]
  },
  "h": [
    0,
    0,
    0,
    5,
    184,
    462,
    520,
    199,
    0,
    0,
    0,
    0
  ],
  "price": {
    "n": 1370,
    "min": 650000,
    "p25": 1487250,
    "median": 2581500,
    "p75": 3623250,
    "max": 35270000,
    "mean": 2773682
  },
  "size": {
    "n": 1370,
    "min": 351,
    "p25": 601,
    "median": 853,
    "p75": 1218,
    "max": 8643,
    "mean": 1087
  },
  "starting_price": {
    "n": 30,
    "min": 399000,
    "p25": 654131,
    "median": 875000,
    "p75": 1196500,
    "max": 3670000,
    "mean": 1058504
  },
  "by_bedroom": [
    {
      "bedrooms": "Studio",
      "n": 203,
      "psf": {
        "n": 203,
        "min": 829,
        "p25": 2391,
        "median": 2422,
        "p75": 3752,
        "max": 5063,
        "mean": 3072
      },
      "price": {
        "n": 203,
        "min": 650000,
        "p25": 949500,
        "median": 1057000,
        "p75": 1522000,
        "max": 2264000,
        "mean": 1284275
      },
      "size": {
        "n": 203,
        "min": 351,
        "p25": 394,
        "median": 411,
        "p75": 439,
        "max": 784,
        "mean": 420
      }
    },
    {
      "bedrooms": "1 BR",
      "n": 517,
      "psf": {
        "n": 517,
        "min": 1051,
        "p25": 2187,
        "median": 2421,
        "p75": 3436,
        "max": 4666,
        "mean": 2693
      },
      "price": {
        "n": 517,
        "min": 1002857,
        "p25": 1423000,
        "median": 1654000,
        "p75": 2617000,
        "max": 3531000,
        "mean": 1922202
      },
      "size": {
        "n": 517,
        "min": 480,
        "p25": 600,
        "median": 749,
        "p75": 793,
        "max": 1099,
        "mean": 713
      }
    },
    {
      "bedrooms": "2 BR",
      "n": 431,
      "psf": {
        "n": 431,
        "min": 1192,
        "p25": 2577,
        "median": 2858,
        "p75": 3236,
        "max": 4344,
        "mean": 2861
      },
      "price": {
        "n": 431,
        "min": 1297461,
        "p25": 3303000,
        "median": 3514000,
        "p75": 3791500,
        "max": 5368000,
        "mean": 3346934
      },
      "size": {
        "n": 431,
        "min": 848,
        "p25": 1107,
        "median": 1178,
        "p75": 1236,
        "max": 1536,
        "mean": 1167
      }
    }
  ],
  "by_type": [
    {
      "type": "apartment",
      "n": 1265,
      "psf_median": 2635,
      "psf": {
        "n": 1265,
        "min": 829,
        "p25": 2300,
        "median": 2635,
        "p75": 3417,
        "max": 5063,
        "mean": 2784
      },
      "price": {
        "n": 1265,
        "min": 650000,
        "p25": 1465000,
        "median": 2283000,
        "p75": 3467000,
        "max": 10635000,
        "mean": 2505655
      },
      "size": {
        "n": 1265,
        "min": 351,
        "p25": 596,
        "median": 806,
        "p75": 1199,
        "max": 3700,
        "mean": 907
      }
    },
    {
      "type": "townhouse",
      "n": 64,
      "psf_median": 1572,
      "psf": {
        "n": 64,
        "min": 1440,
        "p25": 1537,
        "median": 1572,
        "p75": 1588,
        "max": 1634,
        "mean": 1555
      },
      "price": {
        "n": 64,
        "min": 3670000,
        "p25": 3810000,
        "median": 4447000,
        "p75": 4707000,
        "max": 4923000,
        "mean": 4358781
      },
      "size": {
        "n": 64,
        "min": 2400,
        "p25": 2400,
        "median": 2750,
        "p75": 3100,
        "max": 3100,
        "mean": 2805
      }
    },
    {
      "type": "villa",
      "n": 32,
      "psf_median": 1588,
      "psf": {
        "n": 32,
        "min": 1391,
        "p25": 1522,
        "median": 1588,
        "p75": 1848,
        "max": 1974,
        "mean": 1645
      },
      "price": {
        "n": 32,
        "min": 4900000,
        "p25": 5100000,
        "median": 5614000,
        "p75": 7300000,
        "max": 7800000,
        "mean": 5945594
      },
      "size": {
        "n": 32,
        "min": 3100,
        "p25": 3100,
        "median": 3700,
        "p75": 3950,
        "max": 3950,
        "mean": 3602
      }
    }
  ],
  "pipeline_year": [
    {
      "year": 2023,
      "projects": 2
    },
    {
      "year": 2024,
      "projects": 3
    },
    {
      "year": 2025,
      "projects": 4
    },
    {
      "year": 2026,
      "projects": 5
    }
  ],
  "pipeline_quarter": [
    {
      "quarter": "Q1 2024",
      "projects": 2
    },
    {
      "quarter": "Q4 2024",
      "projects": 1
    },
    {
      "quarter": "Q2 2025",
      "projects": 2
    },
    {
      "quarter": "Q4 2025",
      "projects": 2
    }
  ],
  "sale_status": {
    "on_sale": 15,
    "out_of_stock": 8,
    "sold_out": 6,
    "unknown": 1,
    "available": 1
  },
  "construction_status": {
    "under_construction": 20,
    "completed": 9,
    "unknown": 1,
    "not_started": 1
  },
  "property_types": {
    "apartment": 28,
    "duplex": 5,
    "penthouse": 2,
    "townhouse": 2,
    "villa": 1
  },
  "payment_plans": {
    "n": 86,
    "booking": {
      "n": 86,
      "min": 0,
      "p25": 10,
      "median": 20,
      "p75": 50,
      "max": 100,
      "mean": 34
    },
    "construction": {
      "n": 86,
      "min": 0,
      "p25": 12,
      "median": 36,
      "p75": 50,
      "max": 65,
      "mean": 31
    },
    "handover": {
      "n": 86,
      "min": 0,
      "p25": 0,
      "median": 16,
      "p75": 30,
      "max": 70,
      "mean": 21
    },
    "post_handover": {
      "n": 34,
      "min": 5,
      "p25": 30,
      "median": 37,
      "p75": 50,
      "max": 70,
      "mean": 38
    },
    "pre_handover": {
      "n": 86,
      "min": 30,
      "p25": 52,
      "median": 66,
      "p75": 70,
      "max": 100,
      "mean": 64
    },
    "share_post_handover": 40,
    "share_pre_handover_60_plus": 74,
    "share_pre_handover_50_or_less": 26
  },
  "down_payment": {
    "n": 25,
    "min": 10,
    "p25": 10,
    "median": 10,
    "p75": 10,
    "max": 40,
    "mean": 12
  },
  "cities": {
    "Dubai": 31
  },
  "n_communities": 13,
  "top_communities": [
    {
      "name": "Dubai Maritime City",
      "slug": "dubai-maritime-city",
      "projects": 5
    },
    {
      "name": "Jumeirah Village Circle",
      "slug": "jumeirah-village-circle",
      "projects": 5
    },
    {
      "name": "Dubai Silicon Oasis",
      "slug": "dubai-silicon-oasis",
      "projects": 3
    }
  ],
  "projects": [
    {
      "id": "danube-properties-serenz",
      "opi_id": "opi-ff1ad99641",
      "title": "Serenz",
      "community": "Jumeirah Village Circle",
      "community_slug": "jumeirah-village-circle",
      "city": "Dubai",
      "starting_price": 903000,
      "delivery_quarter": "Q4 2029",
      "delivery_year": 2029,
      "sale_status": "on_sale",
      "construction_status": "under_construction",
      "delivery_status": "off_plan",
      "property_types": "apartment",
      "n_units": 300,
      "psf_median": 2389,
      "pre_handover_pct": 50,
      "post_handover_pct": 50
    },
    {
      "id": "danube-properties-bayz-102",
      "opi_id": "opi-c8c71f2587",
      "title": "Bayz 102",
      "community": "Business Bay",
      "community_slug": "business-bay",
      "city": "Dubai",
      "starting_price": 2525000,
      "delivery_quarter": "Q4 2029",
      "delivery_year": 2029,
      "sale_status": "on_sale",
      "construction_status": "under_construction",
      "delivery_status": "off_plan",
      "property_types": "apartment,penthouse",
      "n_units": 299,
      "psf_median": 2944,
      "pre_handover_pct": 50,
      "post_handover_pct": 50
    }
  ],
  "rank_psf": 34,
  "rank_projects": 21,
  "handover_revisions": {
    "slug": "danube",
    "name": "Danube",
    "n_both_dated": 29,
    "moved_later": 0,
    "moved_earlier": 4,
    "unchanged": 25,
    "later_pct": 0,
    "median_slip_months": null,
    "method": "Projects matched on normalised title plus developer (no shared id exists). A handover counts as moved later only when the stated date moved to a later quarter; the July side often carries a quarter-end date, so a move within the same quarter is rounding, not a revision. Dates that moved earlier are reported but never ranked on, because a date reading earlier on a second portal is far more often disagreement between the two sources than a project pulled forward.",
    "caveat": "Two different portals read three weeks apart, not two reads of one listing: a difference can be a portal disagreement rather than a developer revision. Every further dated read adds a point of comparison.",
    "not_measured": "This is not a delivery rate and not an on-time percentage. It counts stated handover dates that read later on a second portal three weeks after the first. Across the whole set, 362 dates read earlier against 105 that read later. That gap is the noise floor of comparing two portals, and it is why the count matters more than the percentage: read every figure with the number of projects behind it."
  },
  "price_movement": {
    "slug": "danube",
    "name": "Danube",
    "n_both_priced": 29,
    "up": 2,
    "down": 5,
    "unchanged": 22,
    "median_move_pct": -7,
    "method": "Starting price on the same project in two reads three weeks apart, matched on title plus developer. A move counts above 1 percent either way; smaller differences are rounding or a portal formatting choice.",
    "caveat": "Asking prices from two different portals, never transactions. A starting price is the cheapest unit on offer that day, so a lower figure on the second read usually means a cheaper unit came to market, not that the project was repriced; a higher one usually means the cheapest units sold. Lines need at least ten priced projects.",
    "not_measured": "This is not a price index and not a measure of value. It counts projects whose quoted starting price differed between two reads three weeks apart, with the direction. Use it as a liveness signal (which communities are moving inventory) and quote the count of projects behind every line."
  }
}

Trimmed the same way as the community sample above. The live response is not trimmed.

Endpoint

GET /api/v1/market

The two market wide aggregates, Dubai and the UAE, same shape as a community aggregate. The per project list is left out here on purpose, it is thousands of rows and duplicates /api/v1/projects, so pull that endpoint for individual projects.

curl -s "https://offplanindex.com/api/v1/market" \
  -H "x-api-key: YOUR_KEY"
{
  "data": {
    "dubai": {
      "name": "Dubai",
      "slug": "dubai",
      "kind": "market",
      "n_projects": 2351,
      "n_records": 3234,
      "n_delivered_projects": 883,
      "n_priced_projects": 2101,
      "n_projects_with_units": 2532,
      "n_units": 42490,
      "n_units_unit_level": 34894,
      "n_projects_with_plans": 2539,
      "psf": {
        "n": 42490,
        "min": 272,
        "p25": 1608,
        "median": 2021,
        "p75": 2649,
        "max": 18352,
        "mean": 2257
      },
      "psf_hist": {
        "lo": 1021,
        "hi": 4870,
        "step": 160.36,
        "counts": [
          1314,
          2490,
          3275,
          4105,
          4596,
          3661
        ]
      },
      "h": [
        8,
        16,
        127,
        1749,
        11294,
        16675,
        9502,
        2530,
        461,
        117,
        9,
        2
      ],
      "price": {
        "n": 42490,
        "min": 375888,
        "p25": 1249500,
        "median": 1910000,
        "p75": 3081230,
        "max": 500000000,
        "mean": 3210918
      },
      "size": {
        "n": 42490,
        "min": 260,
        "p25": 686,
        "median": 916,
        "p75": 1420,
        "max": 39004,
        "mean": 1320
      },
      "starting_price": {
        "n": 2824,
        "min": 310000,
        "p25": 843892,
        "median": 1500000,
        "p75": 2750000,
        "max": 330000000,
        "mean": 4548031
      },
      "by_bedroom": [
        {
          "bedrooms": "Studio",
          "n": 7540,
          "psf": {
            "n": 7540,
            "min": 510,
            "p25": 1806,
            "median": 2280,
            "p75": 2554,
            "max": 6138,
            "mean": 2392
          },
          "price": {
            "n": 7540,
            "min": 375888,
            "p25": 752000,
            "median": 828000,
            "p75": 1052000,
            "max": 4025641,
            "mean": 927573
          },
          "size": {
            "n": 7540,
            "min": 260,
            "p25": 340,
            "median": 375,
            "p75": 408,
            "max": 1204,
            "mean": 393
          }
        },
        {
          "bedrooms": "1 BR",
          "n": 15866,
          "psf": {
            "n": 15866,
            "min": 279,
            "p25": 1613,
            "median": 1916,
            "p75": 2690,
            "max": 7420,
            "mean": 2158
          },
          "price": {
            "n": 15866,
            "min": 400000,
            "p25": 1230000,
            "median": 1500000,
            "p75": 2089894,
            "max": 9337000,
            "mean": 1704974
          },
          "size": {
            "n": 15866,
            "min": 313,
            "p25": 697,
            "median": 754,
            "p75": 857,
            "max": 3138,
            "mean": 797
          }
        },
        {
          "bedrooms": "2 BR",
          "n": 11413,
          "psf": {
            "n": 11413,
            "min": 414,
            "p25": 1539,
            "median": 2027,
            "p75": 2630,
            "max": 11869,
            "mean": 2196
          },
          "price": {
            "n": 11413,
            "min": 879323,
            "p25": 1880000,
            "median": 2529000,
            "p75": 3412777,
            "max": 31600000,
            "mean": 2928842
          },
          "size": {
            "n": 11413,
            "min": 639,
            "p25": 1105,
            "median": 1237,
            "p75": 1446,
            "max": 5645,
            "mean": 1317
          }
        }
      ],
      "by_type": [
        {
          "type": "apartment",
          "n": 38559,
          "psf_median": 2084,
          "psf": {
            "n": 38559,
            "min": 310,
            "p25": 1644,
            "median": 2084,
            "p75": 2722,
            "max": 17223,
            "mean": 2287
          },
          "price": {
            "n": 38559,
            "min": 375888,
            "p25": 1204988,
            "median": 1725232,
            "p75": 2640000,
            "max": 175000000,
            "mean": 2557130
          },
          "size": {
            "n": 38559,
            "min": 260,
            "p25": 662,
            "median": 835,
            "p75": 1240,
            "max": 25317,
            "mean": 1029
          }
        },
        {
          "type": "townhouse",
          "n": 1642,
          "psf_median": 1351,
          "psf": {
            "n": 1642,
            "min": 272,
            "p25": 1108,
            "median": 1351,
            "p75": 1684,
            "max": 12862,
            "mean": 1481
          },
          "price": {
            "n": 1642,
            "min": 535516,
            "p25": 2799860,
            "median": 3643000,
            "p75": 4340000,
            "max": 68000000,
            "mean": 3885348
          },
          "size": {
            "n": 1642,
            "min": 1250,
            "p25": 2041,
            "median": 2403,
            "p75": 2906,
            "max": 15000,
            "mean": 2612
          }
        },
        {
          "type": "villa",
          "n": 1603,
          "psf_median": 1770,
          "psf": {
            "n": 1603,
            "min": 538,
            "p25": 1515,
            "median": 1770,
            "p75": 1990,
            "max": 18352,
            "mean": 1835
          },
          "price": {
            "n": 1603,
            "min": 2000000,
            "p25": 4571000,
            "median": 7289481,
            "p75": 13435000,
            "max": 500000000,
            "mean": 11779165
          },
          "size": {
            "n": 1603,
            "min": 1679,
            "p25": 3158,
            "median": 4162,
            "p75": 6774,
            "max": 39004,
            "mean": 5723
          }
        }
      ],
      "pipeline_year": [
        {
          "year": 2020,
          "projects": 26
        },
        {
          "year": 2021,
          "projects": 19
        },
        {
          "year": 2022,
          "projects": 34
        },
        {
          "year": 2023,
          "projects": 161
        }
      ],
      "pipeline_quarter": [
        {
          "quarter": "Q1 2024",
          "projects": 50
        },
        {
          "quarter": "Q2 2024",
          "projects": 37
        },
        {
          "quarter": "Q3 2024",
          "projects": 39
        },
        {
          "quarter": "Q4 2024",
          "projects": 107
        }
      ],
      "sale_status": {
        "on_sale": 805,
        "out_of_stock": 733,
        "sold_out": 546,
        "unknown": 346,
        "booking_started": 344,
        "available": 277,
        "might_be_sold_out": 83,
        "waiting_for_sales_start": 72,
        "presale": 24,
        "start_of_sales": 2,
        "announced": 2
      },
      "construction_status": {
        "under_construction": 2000,
        "completed": 816,
        "unknown": 346,
        "not_started": 72
      },
      "property_types": {
        "apartment": 1841,
        "villa": 367,
        "townhouse": 306,
        "duplex": 233,
        "penthouse": 193,
        "land": 3
      },
      "payment_plans": {
        "n": 3315,
        "booking": {
          "n": 3315,
          "min": 0,
          "p25": 10,
          "median": 20,
          "p75": 20,
          "max": 100,
          "mean": 25
        },
        "construction": {
          "n": 3315,
          "min": 0,
          "p25": 20,
          "median": 40,
          "p75": 50,
          "max": 100,
          "mean": 36
        },
        "handover": {
          "n": 3315,
          "min": 0,
          "p25": 10,
          "median": 30,
          "p75": 50,
          "max": 90,
          "mean": 31
        },
        "post_handover": {
          "n": 561,
          "min": 5,
          "p25": 30,
          "median": 40,
          "p75": 50,
          "max": 90,
          "mean": 42
        },
        "pre_handover": {
          "n": 3315,
          "min": 0,
          "p25": 50,
          "median": 60,
          "p75": 80,
          "max": 100,
          "mean": 61
        },
        "share_post_handover": 17,
        "share_pre_handover_60_plus": 57,
        "share_pre_handover_50_or_less": 41
      },
      "down_payment": {
        "n": 1855,
        "min": 1,
        "p25": 10,
        "median": 10,
        "p75": 20,
        "max": 70,
        "mean": 15
      },
      "cities": {
        "Dubai": 3234
      }
    },
    "uae": {
      "name": "UAE",
      "slug": "uae",
      "kind": "market",
      "n_projects": 2962,
      "n_records": 4045,
      "n_delivered_projects": 1083,
      "n_priced_projects": 2643,
      "n_projects_with_units": 3114,
      "n_units": 52882,
      "n_units_unit_level": 43771,
      "n_projects_with_plans": 3135,
      "psf": {
        "n": 52882,
        "min": 259,
        "p25": 1548,
        "median": 2008,
        "p75": 2675,
        "max": 24165,
        "mean": 2200
      },
      "psf_hist": {
        "lo": 695,
        "hi": 4675,
        "step": 165.83,
        "counts": [
          1152,
          1291,
          1723,
          3491,
          3785,
          4829
        ]
      },
      "h": [
        18,
        408,
        1105,
        3304,
        12941,
        18939,
        12403,
        3125,
        509,
        118,
        9,
        3
      ],
      "price": {
        "n": 52882,
        "min": 163665,
        "p25": 1252999,
        "median": 1915160,
        "p75": 3156000,
        "max": 500000000,
        "mean": 3244700
      },
      "size": {
        "n": 52882,
        "min": 260,
        "p25": 701,
        "median": 992,
        "p75": 1512,
        "max": 39004,
        "mean": 1390
      },
      "starting_price": {
        "n": 3525,
        "min": 163665,
        "p25": 851337,
        "median": 1520999,
        "p75": 2800000,
        "max": 330000000,
        "mean": 4351529
      },
      "by_bedroom": [
        {
          "bedrooms": "Studio",
          "n": 8104,
          "psf": {
            "n": 8104,
            "min": 259,
            "p25": 1812,
            "median": 2290,
            "p75": 2662,
            "max": 6848,
            "mean": 2406
          },
          "price": {
            "n": 8104,
            "min": 163665,
            "p25": 756192,
            "median": 835000,
            "p75": 1283448,
            "max": 8161512,
            "mean": 958200
          },
          "size": {
            "n": 8104,
            "min": 260,
            "p25": 341,
            "median": 380,
            "p75": 422,
            "max": 1290,
            "mean": 402
          }
        },
        {
          "bedrooms": "1 BR",
          "n": 19139,
          "psf": {
            "n": 19139,
            "min": 279,
            "p25": 1549,
            "median": 1888,
            "p75": 2667,
            "max": 7420,
            "mean": 2092
          },
          "price": {
            "n": 19139,
            "min": 313245,
            "p25": 1169999,
            "median": 1459000,
            "p75": 2078278,
            "max": 12369821,
            "mean": 1681919
          },
          "size": {
            "n": 19139,
            "min": 313,
            "p25": 697,
            "median": 771,
            "p75": 899,
            "max": 3138,
            "mean": 824
          }
        },
        {
          "bedrooms": "2 BR",
          "n": 15722,
          "psf": {
            "n": 15722,
            "min": 292,
            "p25": 1459,
            "median": 2014,
            "p75": 2690,
            "max": 11869,
            "mean": 2131
          },
          "price": {
            "n": 15722,
            "min": 551561,
            "p25": 1730000,
            "median": 2437416,
            "p75": 3331000,
            "max": 31600000,
            "mean": 2822493
          },
          "size": {
            "n": 15722,
            "min": 639,
            "p25": 1083,
            "median": 1245,
            "p75": 1490,
            "max": 5903,
            "mean": 1334
          }
        }
      ],
      "by_type": [
        {
          "type": "apartment",
          "n": 47448,
          "psf_median": 2047,
          "psf": {
            "n": 47448,
            "min": 259,
            "p25": 1580,
            "median": 2047,
            "p75": 2718,
            "max": 17223,
            "mean": 2217
          },
          "price": {
            "n": 47448,
            "min": 163665,
            "p25": 1200000,
            "median": 1721996,
            "p75": 2655000,
            "max": 175000000,
            "mean": 2512676
          },
          "size": {
            "n": 47448,
            "min": 260,
            "p25": 684,
            "median": 894,
            "p75": 1285,
            "max": 25317,
            "mean": 1076
          }
        },
        {
          "type": "villa",
          "n": 2210,
          "psf_median": 1774,
          "psf": {
            "n": 2210,
            "min": 461,
            "p25": 1427,
            "median": 1774,
            "p75": 2099,
            "max": 24165,
            "mean": 1949
          },
          "price": {
            "n": 2210,
            "min": 2000000,
            "p25": 4811250,
            "median": 7807400,
            "p75": 13795710,
            "max": 500000000,
            "mean": 12420362
          },
          "size": {
            "n": 2210,
            "min": 1679,
            "p25": 3275,
            "median": 4406,
            "p75": 6670,
            "max": 39004,
            "mean": 5740
          }
        },
        {
          "type": "townhouse",
          "n": 1936,
          "psf_median": 1320,
          "psf": {
            "n": 1936,
            "min": 272,
            "p25": 1110,
            "median": 1320,
            "p75": 1745,
            "max": 12862,
            "mean": 1553
          },
          "price": {
            "n": 1936,
            "min": 535516,
            "p25": 2800000,
            "median": 3664500,
            "p75": 4387213,
            "max": 68000000,
            "mean": 4123655
          },
          "size": {
            "n": 1936,
            "min": 1250,
            "p25": 2117,
            "median": 2418,
            "p75": 3014,
            "max": 15000,
            "mean": 2657
          }
        }
      ],
      "pipeline_year": [
        {
          "year": 2020,
          "projects": 27
        },
        {
          "year": 2021,
          "projects": 21
        },
        {
          "year": 2022,
          "projects": 42
        },
        {
          "year": 2023,
          "projects": 185
        }
      ],
      "pipeline_quarter": [
        {
          "quarter": "Q1 2024",
          "projects": 59
        },
        {
          "quarter": "Q2 2024",
          "projects": 48
        },
        {
          "quarter": "Q3 2024",
          "projects": 48
        },
        {
          "quarter": "Q4 2024",
          "projects": 136
        }
      ],
      "sale_status": {
        "on_sale": 1014,
        "out_of_stock": 871,
        "sold_out": 628,
        "unknown": 471,
        "available": 414,
        "booking_started": 364,
        "might_be_sold_out": 173,
        "waiting_for_sales_start": 73,
        "presale": 32,
        "announced": 3,
        "start_of_sales": 2
      },
      "construction_status": {
        "under_construction": 2481,
        "completed": 977,
        "unknown": 471,
        "not_started": 116
      },
      "property_types": {
        "apartment": 2234,
        "villa": 504,
        "townhouse": 405,
        "duplex": 306,
        "penthouse": 258,
        "land": 3
      },
      "payment_plans": {
        "n": 4146,
        "booking": {
          "n": 4146,
          "min": 0,
          "p25": 10,
          "median": 20,
          "p75": 20,
          "max": 100,
          "mean": 24
        },
        "construction": {
          "n": 4146,
          "min": 0,
          "p25": 20,
          "median": 40,
          "p75": 50,
          "max": 100,
          "mean": 36
        },
        "handover": {
          "n": 4146,
          "min": 0,
          "p25": 10,
          "median": 35,
          "p75": 50,
          "max": 90,
          "mean": 33
        },
        "post_handover": {
          "n": 674,
          "min": 5,
          "p25": 30,
          "median": 40,
          "p75": 50,
          "max": 95,
          "mean": 43
        },
        "pre_handover": {
          "n": 4146,
          "min": 0,
          "p25": 44,
          "median": 60,
          "p75": 75,
          "max": 100,
          "mean": 60
        },
        "share_post_handover": 16,
        "share_pre_handover_60_plus": 54,
        "share_pre_handover_50_or_less": 44
      },
      "down_payment": {
        "n": 2275,
        "min": 1,
        "p25": 10,
        "median": 10,
        "p75": 20,
        "max": 70,
        "mean": 14
      },
      "cities": {
        "Dubai": 3234,
        "Abu Dhabi": 369,
        "Sharjah": 202,
        "Ras Al Khaimah": 151,
        "Ajman": 55,
        "Umm Al Quwain": 30,
        "Al Ain": 2,
        "Fujairah": 2
      }
    }
  },
  "meta": {
    "note": "Each group's per project list is omitted here, use /api/v1/projects to list individual projects."
  }
}

Same trimming as the community and developer samples above, applied only for display on this page.

Auth, /api/v1/sync only

HMAC per key, not x-api-key

/api/v1/sync is the endpoint the 499/mo data licence and luxurylocated.com pull from on a schedule, so it is keyed to a consumer, not just to a request: each consumer gets a key id and a secret, issued off band, never self-service. Every request is signed rather than carrying the secret itself, so the secret never travels over the wire and a captured request cannot be replayed outside its five minute window.

ParamTypeNotes
x-opi-key-idheader, stringThe key id you were issued. Unrecognised or missing returns 401 unauthorized.
x-opi-timestampheader, unix secondsRejected with 401 if more than 300 seconds from server time, in either direction.
x-opi-signatureheader, hex stringhex HMAC-SHA256(secret, `${method}\n${path}\n${sortedQuery}\n${timestamp}`), see the worked example below.

method is the HTTP method, upper case. path is the URL path with no query string, for example /api/v1/sync. sortedQuery is the request's own query string with its parameters sorted by key (ties keep their original relative order), each pair re-escaped as encodeURIComponent(key)=encodeURIComponent(value) (not the raw bytes on the wire: a decoded value can itself contain & or =, and signing the undecoded form would let one signature match more than one set of parameters), joined with &, and an empty string when there are no query params at all. timestamp is the same string as the x-opi-timestamp header, not re-parsed. The four pieces join with a single \n between each.

Worked example, fake key and secret, never a real one:

key id:      example-key
secret:      example-secret-do-not-use
method:      GET
path:        /api/v1/sync
query:       ?limit=500&since=MjAyNi0wOS0wMVQwMDowMDowMFoAYWJj
timestamp:   1757000000

sortedQuery: limit=500&since=MjAyNi0wOS0wMVQwMDowMDowMFoAYWJj
             (already alphabetical here: "limit" sorts before "since")

message to sign, four lines joined by \n:
  GET
  /api/v1/sync
  limit=500&since=MjAyNi0wOS0wMVQwMDowMDowMFoAYWJj
  1757000000

signature = hex(HMAC-SHA256("example-secret-do-not-use", message))
          = 6578512a1dea3509ae6fa61b8a86473349a2763fc1c16ea74effd4286c67b1a7

curl -s "https://offplanindex.com/api/v1/sync?limit=500&since=MjAyNi0wOS0wMVQwMDowMDowMFoAYWJj" \
  -H "x-opi-key-id: example-key" \
  -H "x-opi-timestamp: 1757000000" \
  -H "x-opi-signature: 6578512a1dea3509ae6fa61b8a86473349a2763fc1c16ea74effd4286c67b1a7"

That timestamp is long past its 300 second window, so this exact request 401s if you actually send it; it is here to let you verify your own signing code produces the same hex string byte for byte, not to be replayed. integrations/luxurylocated/sync.mjs in the repo signs every request the same way, in about twenty lines, if you want working code over prose.

Rate limit is 60 requests per minute per key id, tracked in the memory of the server instance handling the request (the same single instance caveat as the 600/min limit above). Over it returns 429 with Retry-After.

Endpoint, HMAC signed

GET /api/v1/sync

The delta endpoint for downstream consumers: a cursor walk over every off-plan project, in the same shape as /api/v1/projects/{id} plus updated_at and content_hash, and a tombstone list for anything that has left the off-plan set since your last call. See HMAC for /sync above for auth, and freshness for how updated_at is derived.

ParamTypeNotes
sinceopaque cursor, optionalFrom a previous call's data.next_cursor. Omit to walk from the start of the corpus.
limitintegerDefault 500, maximum 1000.

data.next_cursor is never a sentinel: it is present on every response, pointing just past the last record or tombstone you were sent. Keep paging with it. The end of a walk is an empty records AND tombstones array, not a null cursor. Save that response's next_cursor as your resume point: call again later with the same value and you get the same empty page back, echoing that cursor, until something upstream actually changes. That is by design, not a bug to poll around: it means an hourly cron and a five minute cron behave the same way when nothing moved, they just do more of the same no-op call.

A record whose content_hash has not changed since your last sync keeps the exact same updated_at, so it never reappears on a later page once you have passed it, even across a corpus rebuild that touches every row in the same second. Tombstones ({ id, deleted_at }) are ids that were off-plan on a previous build and are not any more, whether delivered or dropped from the corpus outright; a tombstone id is stamped once and never re-dated on a later build that still does not see it.

ts=$(date +%s)
sig=$(printf 'GET\n/api/v1/sync\nlimit=500\n%s' "$ts" | openssl dgst -sha256 -hmac "$OFFPLANINDEX_SYNC_SECRET" -hex | cut -d' ' -f2)
curl -s "https://offplanindex.com/api/v1/sync?limit=500" \
  -H "x-opi-key-id: $OFFPLANINDEX_SYNC_KEY_ID" \
  -H "x-opi-timestamp: $ts" \
  -H "x-opi-signature: $sig"
{
  "data": {
    "records": [
      {
        "id": "7th-key-nexara-tower",
        "opi_id": "opi-c6c4a81b87",
        "title": "Nexara Tower",
        "developer": "7th Key",
        "developer_slug": "7th-key",
        "community": "Jumeirah Village Circle",
        "community_slug": "jumeirah-village-circle",
        "city": "Dubai",
        "starting_price": 1100000,
        "delivery_quarter": "Q2 2028",
        "delivery_year": 2028,
        "sale_status": "on_sale",
        "construction_status": "under_construction",
        "property_types": "apartment,duplex",
        "bedrooms": "1,2,3,4,5",
        "n_units": 12,
        "n_plans": 2,
        "lat": 25.05876771605937,
        "lng": 55.211747690389494,
        "psf_median": 1773,
        "pre_handover_pct": 50,
        "post_handover_pct": 0,
        "psf_position": {
          "community": {
            "percentile_estimate": 60,
            "comparator_median_psf": 1654,
            "delta_vs_median_pct": 7.2,
            "note": "percentile_estimate is a linear interpolation between the comparator's published quantiles (min, p25, median, p75, max), not a rank over the raw distribution."
          },
          "dubai_market": {
            "percentile_estimate": 35,
            "comparator_median_psf": 2021,
            "delta_vs_median_pct": -12.3,
            "note": "percentile_estimate is a linear interpolation between the comparator's published quantiles (min, p25, median, p75, max), not a rank over the raw distribution."
          }
        },
        "updated_at": "2026-07-28T00:00:00Z",
        "content_hash": "bb1d257e6621"
      },
      {
        "id": "a-j-g-jaddaf-l-l-c-lumiere-heights",
        "opi_id": "opi-663a003505",
        "title": "Lumiere Heights",
        "developer": "A J G Jaddaf L. L. C",
        "developer_slug": "a-j-g-jaddaf-l-l-c",
        "community": "Culture Village",
        "community_slug": "culture-village",
        "city": "Dubai",
        "starting_price": 700000,
        "delivery_quarter": "Q2 2027",
        "delivery_year": 2027,
        "sale_status": null,
        "construction_status": null,
        "property_types": null,
        "bedrooms": null,
        "n_units": 0,
        "n_plans": 0,
        "lat": null,
        "lng": null,
        "psf_median": null,
        "pre_handover_pct": null,
        "post_handover_pct": null,
        "psf_position": {
          "community": null,
          "dubai_market": null
        },
        "updated_at": "2026-07-28T00:00:00Z",
        "content_hash": "d8234ce95b6d"
      }
    ],
    "tombstones": [],
    "next_cursor": "MjAyNi0wNy0yOFQwMDowMDowMFoAYS1qLWctamFkZGFmLWwtbC1jLWx1bWllcmUtaGVpZ2h0cw",
    "schema_version": 1,
    "dataset_version": "2026-10",
    "build_date": "2026-10-02"
  },
  "meta": {
    "total": 2982,
    "skipped": 0,
    "limit": 2,
    "since_requested": null,
    "corpus_date": "2026-07-30",
    "merge_date": "2026-08-18",
    "cursor_note": "Store data.dataset_version with your cursor. A cursor walk delivers records whose read date is newer than your cursor; it does not redeliver a record whose shape gained a field, because a record's updated_at is the date we read it, not the date we rebuilt. When dataset_version changes and you want new fields on records you already hold, walk once from the beginning. since is an opaque cursor from a previous call's data.next_cursor, not a timestamp you construct yourself. Omit it to walk from the beginning. Records sort by (updated_at, id); a rebuild that touches every row in the same second does not reorder or reissue a cursor, since an unchanged content_hash keeps its existing updated_at. data.next_cursor is present on every response, never a sentinel: keep paging with it and detect the end of a walk by an empty records AND tombstones array, not by next_cursor being null. Once you reach that empty page, save its next_cursor as your resume point; calling /sync again with that exact value returns empty pages, echoing the same cursor back, until something genuinely changes upstream.",
    "license": "Derived analytics only: aggregates and analysis computed by OffplanIndex from developer published project facts and publicly observed listings. Not a redistribution of any portal's database, and no image URLs are served. See /data-policy for the licence and /data to buy a key."
  }
}

Live sample built by calling the same buildSyncPayload the route calls, limit=2, from the start of the corpus (no since). The real endpoint requires the HMAC headers above; this sample bypasses HTTP entirely so the shape on this page can never drift from the implementation.

Endpoint, unkeyed

GET /api/v1/meta

Corpus counts and build date. No x-api-key required, safe for a health check.

curl -s "https://offplanindex.com/api/v1/meta"
{
  "data": {
    "build_date": "2026-10-02",
    "corpus_date": "2026-07-30",
    "merge_date": "2026-08-18",
    "data_month": "July 2026",
    "corpus_date_long": "30 July 2026",
    "merge_date_long": "18 August 2026",
    "build_date_long": "2 October 2026",
    "n_projects": 2962,
    "n_records": 4045,
    "n_delivered_projects": 1083,
    "n_delayed_projects": 169,
    "n_priced_projects": 2643,
    "n_unpriced_projects": 319,
    "n_units_raw": 68875,
    "n_units_valid": 59806,
    "n_units_benchmark": 52882,
    "n_units_size_flagged": 36,
    "n_units_unit_level": 43771,
    "n_projects_with_units": 2332,
    "n_projects_without_units": 630,
    "n_projects_with_plans": 2270,
    "n_communities": 231,
    "n_developers": 681,
    "n_dubai_projects": 2351,
    "n_with_coords": 2610,
    "n_without_coords": 352,
    "n_with_community": 2951,
    "n_duplicates_merged": 121,
    "n_developer_variants_merged": 92,
    "latest_scrape": "2026-07-30",
    "n_from_watcher": 78,
    "n_offplan_from_watcher": 77,
    "watcher_date": "2026-09-30",
    "watcher_date_long": "30 September 2026",
    "psf_hist_bins": 12,
    "psf_hist_lo": 250,
    "psf_hist_hi": 25000,
    "psf_hist_scale": "log10",
    "psf_hist_edges": [
      250,
      366.9,
      538.6,
      790.6,
      1160.4,
      1703.2,
      2500,
      3669.5,
      5386.1,
      7905.7,
      11604,
      17032.3,
      25000
    ],
    "corpus_age_days": 64,
    "watcher_age_days": 2,
    "newest_data_date": "2026-09-30",
    "data_age_days": 2,
    "freshness_sla_days": 14,
    "stale": true,
    "freshness_checked": "2026-10-02"
  },
  "meta": {
    "note": "Unkeyed health check endpoint. No x-api-key required.",
    "freshness": "Sources read to 2026-07-30, corpus merged 2026-08-18, bundle built 2026-10-02.",
    "counts": "n_projects is the off-plan count. n_delivered_projects have already handed over and are excluded from it and from every price benchmark; n_records is the two together."
  }
}

Derived analytics only: aggregates and analysis computed by OffplanIndex from developer published project facts and publicly observed listings. Not a redistribution of any portal's database, and no image URLs are served. See /data-policy for the licence and /data to buy a key. Not investment advice. Questions go to hello@offplanindex.com.