PropertyIndex Market index API
Monthly PIX index levels and supporting observations by segment.
Monthly PIX index levels and supporting observations by segment.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/propertyindex-ae:propertyindex-ae-market-index-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: PIX Market index API
version: 1.0.0
description: 'The PIX API gives licensed organisations direct, programmatic access to the
Dubai Land Department records that PropertyIndex models: every registered
sale, every registered tenancy period, its location labels and floor,
and the PIX market index, as flat rows you can pull into your
own systems. Each record carries its registered figures and our per-record
calculations (price and rent per square foot, normalised property type and
bedrooms). Location and floor labels are not identifiers or proof of a
unique physical structure. Unit numbers, persistent property identities and cross-record histories are
not part of the API. ## Location labels
Sales and rentals carry the record''s assigned location as labels:
- `location_path` is the full published path, an ordered list of labels
from Dubai to the assigned location. - `highest_location_level` is the first node after Dubai, regardless of
its kind. - `lowest_location_level` is the final assigned location node, which
may be an area, community, subcommunity, tower or the city itself. For a sale in Beach Vista Tower 1:
```json
{
"location_path": ["Dubai", "Dubai Harbour", "Emaar Beachfront", "Beach Vista", "Beach Vista Tower 1"],
"highest_location_level": "Dubai Harbour",
"lowest_location_level": "Beach Vista Tower 1"
}
```
When only one node follows Dubai, both level fields contain that node''s
label. For a city-only assignment, the path is `["Dubai"]`, the highest
value is null and the lowest is `Dubai`. All three are null when the
location is unresolved; a path is never partial. Labels are names, not identifiers, and names can repeat across the tree. `location_path` is returned by default and can be requested with
`fields`, but it is not a filter, sort or cursor field. To scope a query
to a place, use `location_id`. ## Base URL
All endpoints are served from `https://api.propertyindex.ae`. Only `GET`
requests are accepted. Responses are JSON. ## Authentication
Send your key as a bearer token on every request:
```
Authorization: Bearer pix_api_XXXXXXXXXX_... ```
Keys are issued to your organisation, not to a person. Keep them server-side. A key can be revoked and reissued at any time on request. ## Allowance and rate limits
Authenticated responses carry your meter in headers: `PIX-Usage-Used`,
`PIX-Usage-Allowance`, `PIX-Usage-Remaining`, `PIX-Usage-Period` and,
where the period has an end, `PIX-Usage-Period-End`. `PIX-Usage-Unit: records` identifies the unit. `PIX-Usage-Remaining`
means included records remaining, excluding optional overage capacity. `GET /v1/usage` returns these figures and the effective
`recordsRemaining`, including any enabled overage capacity, without
consuming records. The paid plan is AED 5,000 per month and includes 1,000,000 sales and rental
records per billing period, shared across the organisation''s API keys. Each actual row returned by a sales or rentals
list counts as one record; a successful single-record response counts as
one. Repeated retrievals count again. Query and validation failures and
empty responses consume no records. Locations, market index, documentation
and usage are unmetered. Optional overage costs AED 2 per 1,000 records, prorated to the nearest fils
on the billing period total: `overageCostMinor = round(overageRecords / 5)`. Overage is off by default. An organisation owner or admin must opt in and
set a finite monthly overage spending cap, excluding the subscription and
any applicable taxes. `overageCapMinor` is that cap
in fils; when enabled, each fils adds five records to `recordLimit` above
the included allowance. `remaining` counts only unused included records;
`recordsRemaining` counts capacity up to the effective `recordLimit`. Demo grants have a fixed record allowance with no overage. Both demo and paid keys enforce a hard limit. If the actual response would
exceed `recordsRemaining`, the whole response is rejected with
`429 allowance_exhausted` and no records are charged.'
contact:
name: PropertyIndex
email: info@propertyindex.ae
url: https://www.propertyindex.ae
termsOfService: https://www.propertyindex.ae/terms-of-service
license:
name: PIX API licence (proprietary)
url: https://www.propertyindex.ae/terms-of-service
servers:
- url: https://api.propertyindex.ae
security:
- bearerAuth: []
tags:
- name: Market index
description: Monthly PIX index levels and supporting observations by segment.
paths:
/v1/market-index:
get:
tags:
- Market index
operationId: listMarketIndexObservations
summary: List observations
description: 'Monthly PIX index levels by segment. Monthly PropertyIndex (PIX) levels and supporting Dubai market observations. Unmetered: consumes no records. The burst rate limit still applies.'
parameters:
- name: sort
in: query
description: Field to sort by. Default segment.
required: false
schema:
type: string
enum:
- segment
- month
- index_level
- mom_pct
- yoy_pct
- median_psf
- sales_count
- name: order
in: query
description: Sort direction. Default asc.
required: false
schema:
type: string
enum:
- asc
- desc
- name: limit
in: query
description: Rows per page, 1 to 1,000. Default 100.
required: false
schema:
type: integer
minimum: 1
maximum: 1000
- name: page
in: query
description: Numbered page, starting at 1. Send one positive integer. Uses limit (default 100, maximum 1000). Cannot be combined with cursor. Omit both to start at the first page. The computed offset (page - 1) * limit must also be a safe integer. Large page numbers may be slower because earlier rows must be skipped; prefer cursors for sequential bulk walks.
required: false
schema:
type: integer
minimum: 1
maximum: 9007199254740991
- name: cursor
in: query
description: Opaque cursor from a previous response's next_cursor. Send the same sort, order and filters, including location_id and location_search where supported. Cannot be combined with page. To continue a numbered page using next_cursor, remove page.
required: false
schema:
type: string
- name: fields
in: query
description: Comma-separated list of fields to return. Default all.
required: false
schema:
type: string
example: segment,month
- name: segment
in: query
description: Exact match on segment. Repeat the parameter to match any of several values.
required: false
schema:
type: array
items:
type: string
enum:
- residential
- apartment
- villa
- ready
- offplan
style: form
explode: true
- name: segment_not
in: query
description: Exclude rows where segment equals this value.
required: false
schema:
type: string
enum:
- residential
- apartment
- villa
- ready
- offplan
- name: segment_null
in: query
description: true keeps only rows with no segment; false keeps only rows with one.
required: false
schema:
type: boolean
- name: month
in: query
description: Exact match on month. Repeat the parameter to match any of several values.
required: false
schema:
type: array
items:
type: string
format: date
example: '2026-01-01'
style: form
explode: true
- name: month_not
in: query
description: Exclude rows where month equals this value.
required: false
schema:
type: string
format: date
example: '2026-01-01'
- name: month_null
in: query
description: true keeps only rows with no month; false keeps only rows with one.
required: false
schema:
type: boolean
- name: month_from
in: query
description: month on or after this date.
required: false
schema:
type: string
format: date
example: '2026-01-01'
- name: month_to
in: query
description: month on or before this date.
required: false
schema:
type: string
format: date
example: '2026-01-01'
- name: index_level
in: query
description: Exact match on index_level. Repeat the parameter to match any of several values.
required: false
schema:
type: array
items:
type: number
style: form
explode: true
- name: index_level_not
in: query
description: Exclude rows where index_level equals this value.
required: false
schema:
type: number
- name: index_level_null
in: query
description: true keeps only rows with no index_level; false keeps only rows with one.
required: false
schema:
type: boolean
- name: index_level_min
in: query
description: index_level greater than or equal to.
required: false
schema:
type: number
- name: index_level_max
in: query
description: index_level less than or equal to.
required: false
schema:
type: number
- name: mom_pct
in: query
description: Exact match on mom_pct. Repeat the parameter to match any of several values.
required: false
schema:
type: array
items:
type: number
style: form
explode: true
- name: mom_pct_not
in: query
description: Exclude rows where mom_pct equals this value.
required: false
schema:
type: number
- name: mom_pct_null
in: query
description: true keeps only rows with no mom_pct; false keeps only rows with one.
required: false
schema:
type: boolean
- name: mom_pct_min
in: query
description: mom_pct greater than or equal to.
required: false
schema:
type: number
- name: mom_pct_max
in: query
description: mom_pct less than or equal to.
required: false
schema:
type: number
- name: yoy_pct
in: query
description: Exact match on yoy_pct. Repeat the parameter to match any of several values.
required: false
schema:
type: array
items:
type: number
style: form
explode: true
- name: yoy_pct_not
in: query
description: Exclude rows where yoy_pct equals this value.
required: false
schema:
type: number
- name: yoy_pct_null
in: query
description: true keeps only rows with no yoy_pct; false keeps only rows with one.
required: false
schema:
type: boolean
- name: yoy_pct_min
in: query
description: yoy_pct greater than or equal to.
required: false
schema:
type: number
- name: yoy_pct_max
in: query
description: yoy_pct less than or equal to.
required: false
schema:
type: number
- name: median_psf
in: query
description: Exact match on median_psf. Repeat the parameter to match any of several values.
required: false
schema:
type: array
items:
type: number
style: form
explode: true
- name: median_psf_not
in: query
description: Exclude rows where median_psf equals this value.
required: false
schema:
type: number
- name: median_psf_null
in: query
description: true keeps only rows with no median_psf; false keeps only rows with one.
required: false
schema:
type: boolean
- name: median_psf_min
in: query
description: median_psf greater than or equal to.
required: false
schema:
type: number
- name: median_psf_max
in: query
description: median_psf less than or equal to.
required: false
schema:
type: number
- name: sales_count
in: query
description: Exact match on sales_count. Repeat the parameter to match any of several values.
required: false
schema:
type: array
items:
type: number
style: form
explode: true
- name: sales_count_not
in: query
description: Exclude rows where sales_count equals this value.
required: false
schema:
type: number
- name: sales_count_null
in: query
description: true keeps only rows with no sales_count; false keeps only rows with one.
required: false
schema:
type: boolean
- name: sales_count_min
in: query
description: sales_count greater than or equal to.
required: false
schema:
type: number
- name: sales_count_max
in: query
description: sales_count less than or equal to.
required: false
schema:
type: number
responses:
'200':
description: One page of rows.
headers:
PIX-Usage-Kind:
description: paid or demo.
schema:
type: string
enum:
- paid
- demo
PIX-Usage-Unit:
description: The usage unit is records.
schema:
type: string
const: records
PIX-Usage-Used:
description: Sales and rental records used in the current period, including this response.
schema:
type: integer
minimum: 0
PIX-Usage-Allowance:
description: Records included in the current period, before optional overage.
schema:
type: integer
minimum: 0
PIX-Usage-Remaining:
description: Included records remaining, excluding overage capacity. GET /v1/usage returns recordsRemaining for effective capacity.
schema:
type: integer
minimum: 0
PIX-Usage-Period:
description: Identifier of the current period.
schema:
type: string
PIX-Usage-Period-End:
description: When the current period ends, if it does.
schema:
type: string
format: date-time
content:
application/json:
schema:
$ref: '#/components/schemas/MarketIndexObservationList'
'400':
description: The request is not valid. The message names the parameter.
headers: {}
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Missing or unrecognised API key.
headers: {}
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: The key is not active, or the organisation holds no licence or demo allowance.
headers: {}
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limit reached, or the response exceeds recordsRemaining (allowance_exhausted). The whole response is rejected without consuming records; request a smaller limit if capacity remains.
headers:
PIX-Usage-Kind:
description: paid or demo.
schema:
type: string
enum:
- paid
- demo
PIX-Usage-Unit:
description: The usage unit is records.
schema:
type: string
const: records
PIX-Usage-Used:
description: Sales and rental records used in the current period, including this response.
schema:
type: integer
minimum: 0
PIX-Usage-Allowance:
description: Records included in the current period, before optional overage.
schema:
type: integer
minimum: 0
PIX-Usage-Remaining:
description: Included records remaining, excluding overage capacity. GET /v1/usage returns recordsRemaining for effective capacity.
schema:
type: integer
minimum: 0
PIX-Usage-Period:
description: Identifier of the current period.
schema:
type: string
PIX-Usage-Period-End:
description: When the current period ends, if it does.
schema:
type: string
format: date-time
Retry-After:
description: Seconds to wait before retrying (rate limit only).
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
Error:
type: object
required:
- error
properties:
error:
type: object
required:
- code
- message
properties:
code:
type: string
example: invalid_parameter
message:
type: string
MarketIndexObservation:
type: object
description: Monthly PropertyIndex (PIX) levels and supporting Dubai market observations.
properties:
segment:
type:
- string
- 'null'
description: 'PIX market segment. Known values: residential, apartment, villa, ready, offplan.'
month:
type:
- string
- 'null'
format: date
description: Index observation month.
index_level:
type:
- number
- 'null'
description: 'PIX index level for the segment and month. Unit: index points.'
mom_pct:
type:
- number
- 'null'
description: 'Month-on-month PIX change. Unit: percent.'
yoy_pct:
type:
- number
- 'null'
description: 'Year-on-year PIX change. Unit: percent.'
median_psf:
type:
- number
- 'null'
description: 'Observed median registered sale PSF. Unit: AED/sqft.'
sales_count:
type:
- number
- 'null'
description: 'Registered sales supporting the observation. Unit: sales.'
MarketIndexObservationList:
type: object
required:
- data
- next_cursor
- as_of
- total
- page
- limit
properties:
data:
type: array
items:
$ref: '#/components/schemas/MarketIndexObservation'
next_cursor:
type:
- string
- 'null'
description: Returned for cursor and numbered pages when more data exists. Pass as cursor to fetch the next page, removing page and preserving sort, order and filters. null on the last page.
as_of:
type:
- string
- 'null'
format: date
description: The DLD delivery date the data was built from.
total:
type: integer
minimum: 0
description: 'Exact number of records matching all filters, including q, location_search and location scopes where supported. Ignores page, limit and cursor. Always included, even on empty pages. Not a snapshot: can change after a data refresh.'
page:
type:
- integer
- 'null'
minimum: 1
maximum: 9007199254740991
description: Requested numbered page, or 1 when neither page nor cursor was supplied. null for cursor requests because a cursor position is not a numbered page.
limit:
type: integer
minimum: 1
maximum: 1000
description: Requested page size, or 100 when omitted. This is the maximum rows requested, not the actual number returned; data can contain fewer rows.
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: 'Your PIX API key, sent as `Authorization: Bearer pix_api_...`.'