Overview

Public CRM API provides secure read-only access for analytics systems, BI/reporting tools,

partner integrations, and data pipelines.

All responses use a standard JSON envelope:

{
  "data": [...],
  "count": 123,
  "page": { "limit": 50, "cursor": "<token>" },
  "meta": { "from": "...", "to": "...", "sortBy": "...", "sortDir": "...", "filters": {} }
}

Authentication

All requests require an API key in the request header:

x-api-key: <YOUR_API_KEY>
ScenarioHTTP StatusError Code
Missing header401MISSING_API_KEY
Invalid key401INVALID_API_KEY
Disabled key / missing permission403FORBIDDEN

Base URLs

Each resource is served by a dedicated base endpoint URL:

ResourceBase URL
Contactshttps://us-central1-kendal-17416.cloudfunctions.net/contacts
Listingshttps://us-central1-kendal-17416.cloudfunctions.net/listings
Inquirieshttps://us-central1-kendal-17416.cloudfunctions.net/inquiries
Dealshttps://us-central1-kendal-17416.cloudfunctions.net/deals
Appointmentshttps://us-central1-kendal-17416.cloudfunctions.net/appointments

Pagination

All list endpoints use cursor-based pagination:

  1. Call the endpoint without a cursor parameter
  2. Read page.cursor from the response
  3. Call the same endpoint with cursor=<value> to get the next page
  4. Stop when cursor is null

Never construct cursors manually — they are opaque tokens signed by the server.

Rate Limits

Rate limits are applied per API key using a sliding window.

HeaderDescription
x-ratelimit-remainingRequests remaining in current window
x-ratelimit-resetUnix timestamp when the window resets

Exceeding the limit returns 429 RATE_LIMIT_EXCEEDED.

Best practices:

  • Use /count endpoints when you only need totals
  • Retry UPSTREAM_SEARCH_ERROR with exponential backoff
  • Use explicit from/to for reproducible reports
  • Log the x-request-id response header for debugging

Errors

All errors use a consistent envelope and are accompanied by an HTTP status code.

CodeHTTPMeaning
MISSING_API_KEY401Missing x-api-key header
INVALID_API_KEY401Key not recognized
FORBIDDEN403Missing endpoint permission
INVALID_QUERY400Invalid query parameter value
INVALID_CURSOR400Cursor invalid or tampered
RATE_LIMIT_EXCEEDED429Request rate exceeded for this API key
UPSTREAM_SEARCH_ERROR502Elasticsearch temporary issue — retry with backoff
INTERNAL_ERROR500Unexpected server error

Contacts

Read-only access to CRM contact records. Backed by Elasticsearch index contacts_v2.

GET
/api/v1/contacts

List Contacts

contacts:data

Returns a paginated list of contacts. Supports filtering by status, source, agent, tags, and full-text search.

limit
integer

Range 1-200. Number of records per page.

Default:50
cursor
string

Opaque pagination token from previous page.cursor. Never construct manually.

from
ISO datetime

Inclusive start of date range on lastEditedOn/updatedAt.

Default:now - 90 days
to
ISO datetime

Inclusive end of date range.

Default:now
sortBy
string

Accepted: lastEditedOn, createdOn, updatedAt

Default:lastEditedOn
sortDir
string

Accepted: asc, desc

Default:desc
createdBy
string (CSV)

Filter by creator email. Accepts comma-separated values to match multiple creators (e.g., createdBy=user1@example.com,user2@example.com).

leadAgent
string (CSV)

Filter by lead agent email. Accepts comma-separated values to match multiple agents (e.g., leadAgent=agent1@example.com,agent2@example.com).

assignedAgent
string (CSV)

Filter by assigned agent email. Accepts comma-separated values to match multiple agents. Alias for leadAgent. Also accepts: agentAssigned.

status
string (CSV)

Filter by contact status. Accepts comma-separated values to match multiple statuses (e.g., status=new,active).

source
string (CSV)

Filter by contact source. Accepts comma-separated values to match multiple sources (e.g., source=Imported Contact,Website). Example values: "Imported Contact".

tags
string (CSV)

Filter by contact tags. Accepts comma-separated values to match multiple tags. Possible values: "preLead", "prospect", "client". Alias: tag.

hasEmail
boolean

Only returns contacts with a non-empty email field.

hasPhone
boolean

Only returns contacts with a non-empty phone or phoneNumber field.

q
string

Search contacts by name, email, phone number, source, or status. Supports partial matches (e.g., 'john' finds 'John Smith'). Combine with other filters to narrow results.

data
array

Array of contact objects

contactId
string

Unique contact identifier

firstName
string

Contact name

phone
string

Phone number

email
string

Email address

organisationId
string

Tenant identifier

source
string

Contact source label

count
integer

Total matching contacts across all pages

page
object

Pagination info

limit
integer

Records per page

cursor
string or null

Token for next page. null when exhausted.

meta
object

Applied filters and sort info

from
string

ISO datetime

to
string

ISO datetime

sortBy
string

Field used for sorting (e.g., lastEditedOn, createdOn, updatedAt)

sortDir
string

Sort direction: 'asc' (ascending) or 'desc' (descending)

filters
object

All filters applied to this query (status, source, tags, etc.)

GET
/api/v1/contacts/count

Count Contacts

contacts:count

Returns only the total count and applied filter metadata. Use when you need totals for dashboards and do not need actual records — cheaper on rate limits.

status
string (CSV)

Filter by contact status. Accepts comma-separated values to match multiple statuses (e.g., status=new,active).

source
string (CSV)

Filter by contact source. Accepts comma-separated values to match multiple sources (e.g., source=Imported Contact,Website). Example values: "Imported Contact".

tags
string (CSV)

Filter by contact tags. Accepts comma-separated values to match multiple tags. Possible values: "preLead", "prospect", "client". Alias: tag.

hasEmail
boolean

Only returns contacts with a non-empty email field.

hasPhone
boolean

Only returns contacts with a non-empty phone or phoneNumber field.

createdBy
string (CSV)

Filter by creator email. Accepts comma-separated values to match multiple creators (e.g., createdBy=user1@example.com,user2@example.com).

leadAgent
string (CSV)

Filter by lead agent email. Accepts comma-separated values to match multiple agents (e.g., leadAgent=agent1@example.com,agent2@example.com).

assignedAgent
string (CSV)

Filter by assigned agent email. Accepts comma-separated values to match multiple agents. Alias for leadAgent. Also accepts: agentAssigned.

q
string

Search contacts by name, email, phone number, source, or status. Supports partial matches (e.g., 'john' finds 'John Smith'). Combine with other filters to narrow results.

count
integer

Total number of matching contacts

meta
object

Filter echo

from
string

ISO datetime

to
string

ISO datetime

filters
object

All filters applied to this query (status, source, tags, etc.)

Listings

Read-only access to property listing records. Backed by Elasticsearch index listings_v1.

GET
/api/v1/listings

List Listings

listings:data

Returns a paginated list of property listings. Supports filtering by status, portal, property type, location, price range, and bedrooms.

limit
integer

Range 1-200

Default:50
cursor
string

Opaque pagination token

from
ISO datetime

Inclusive, filters on updatedAt

Default:now - 90 days
to
ISO datetime

Inclusive

Default:now
sortBy
string

Accepted: updatedAt, createdAt

Default:updatedAt
sortDir
string

asc / desc

Default:desc
status
string (CSV)

Filter by listing status. Accepts comma-separated values to match multiple statuses. Possible values: "onMarket", "offTheMarket", "tempOffMarket".

portal
string (CSV)

Filter by portal publication status. Accepts one or more portal names (bayut, dubizzle, propertyFinder, externalWebsite) as comma-separated values (e.g., portal=bayut or portal=bayut,dubizzle).

propertyType
string (CSV)

Filter by property type. Accepts comma-separated values to match multiple types. Possible values: "Apartment / Flat", "Villa / House", "Bungalow", "Duplex", "Penthouse", "Townhouse", "Bulk Unit", "Full Residential Floor", "Whole Building", "Hotel Apartment", "Loft Apartment", "Residential Land / Plot", "Office", "Compound", "Factory", "Farm", "Labor Camp", "Business Centre", "Retail", "Restaurant", "Storage", "Shop", "Showroom", "Co-working Space", "Warehouse", "Half Floor", "Staff Accommodation".

city
string (CSV)

Filter by city. Accepts comma-separated values to match multiple cities (e.g., city=Dubai,Abu Dhabi). Searches across listing location fields.

location
string (CSV)

Filter by location. Accepts comma-separated values to match multiple locations (e.g., location=Downtown,Marina). Searches across listing location fields.

agentEmail
string (CSV)

Filter by assigned agent email. Accepts comma-separated values to match multiple agents (e.g., agentEmail=agent1@example.com,agent2@example.com).

referenceId
string (CSV)

Filter by listing reference ID. Accepts comma-separated values to match multiple listings (e.g., referenceId=KD-1074,KD-1075).

minPrice
number

Minimum price filter. For rentals, filters by rent amount; for sales, filters by asking price.

maxPrice
number

Maximum price filter. For rentals, filters by rent amount; for sales, filters by asking price.

minBedrooms
number

Minimum number of bedrooms.

maxBedrooms
number

Maximum number of bedrooms.

data
array

Array of listing objects

listingId
string

Reference number e.g. KD-1074

organisationId
string

Tenant identifier

title
string

Listing title

propertyType
string

Type of property

city
string

City

listingStatus
string

Current listing status

createdAt
string

ISO datetime of creation

count
integer

Total matching listings

page
object

Pagination

limit
integer

No description provided.

cursor
string or null

No description provided.

meta
object

Filter/sort echo

GET
/api/v1/listings/count

Count Listings

listings:count

Returns only the total count and filter metadata. Ideal for dashboards showing portfolio size or inventory analytics without fetching full records.

status
string (CSV)

Filter by listing status. Accepts comma-separated values to match multiple statuses. Possible values: "onMarket", "offTheMarket", "tempOffMarket".

portal
string (CSV)

Filter by portal publication status. Accepts one or more portal names (bayut, dubizzle, propertyFinder, externalWebsite) as comma-separated values (e.g., portal=bayut or portal=bayut,dubizzle).

propertyType
string (CSV)

Filter by property type. Accepts comma-separated values to match multiple types. Possible values: "Apartment / Flat", "Villa / House", "Bungalow", "Duplex", "Penthouse", "Townhouse", "Bulk Unit", "Full Residential Floor", "Whole Building", "Hotel Apartment", "Loft Apartment", "Residential Land / Plot", "Office", "Compound", "Factory", "Farm", "Labor Camp", "Business Centre", "Retail", "Restaurant", "Storage", "Shop", "Showroom", "Co-working Space", "Warehouse", "Half Floor", "Staff Accommodation".

city
string (CSV)

Filter by city. Accepts comma-separated values to match multiple cities (e.g., city=Dubai,Abu Dhabi). Searches across listing location fields.

location
string (CSV)

Filter by location. Accepts comma-separated values to match multiple locations (e.g., location=Downtown,Marina). Searches across listing location fields.

agentEmail
string (CSV)

Filter by assigned agent email. Accepts comma-separated values to match multiple agents (e.g., agentEmail=agent1@example.com,agent2@example.com).

referenceId
string (CSV)

Filter by listing reference ID. Accepts comma-separated values to match multiple listings (e.g., referenceId=KD-1074,KD-1075).

minPrice
number

Minimum price filter. For rentals, filters by rent amount; for sales, filters by asking price.

maxPrice
number

Maximum price filter. For rentals, filters by rent amount; for sales, filters by asking price.

minBedrooms
number

Minimum number of bedrooms.

maxBedrooms
number

Maximum number of bedrooms.

count
integer

Total matching listings

meta
object

Filter echo