Skip to content

Submit an end-client application for a unit

POST
/units/{id}/applications
curl --request POST \
--url https://agent-dev.azizdubai.com/units/1/applications \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <X-API-Key>' \
--data '{ "client": { "first_name": "Ivan", "last_name": "Petrov", "email": "ivan.petrov@example.com", "mobile_number": "+971 50 111 22 33" }, "preferences": { "based_on_unit": true }, "message": "Please call me after 6 pm" }'

Creates four records in the CRM of the company that owns the key: the client, their interests (an opportunity), a draft offer for the selected unit, and the link between the offer and the opportunity. All records are created in a single transaction: either everything is written or nothing is.

Ownership and authorship are taken from the key, as there is no signed-in user behind the request. The key is therefore required: it determines where the application is recorded.

A unit of a draft or deleted project is treated as not found: applications cannot be submitted for units that are not on sale.

The operation is not idempotent: each call creates a new opportunity and a new offer. An existing client is reused if the company already has a client with the same email address or the same mobile number (compared by digits only).

Full details, including the rules for merging interests, are available in docs/api/applications.md.

id
required
integer
>= 1

Unit ID.

Media typeapplication/json
object
client
required
Any of:

The person submitting the application. A first name and at least one contact channel are required: a lead without contact details cannot be followed up, and the email address and mobile number are also used to match existing clients.

object
first_name
string
<= 255 characters
last_name
string | null
<= 255 characters
email
required

Converted to lower case. Must contain @.

string | null
<= 255 characters
mobile_number

Stored as provided, including the country code. Only digits are compared when matching existing clients.

string | null
<= 50 characters
landline_number
string | null
<= 50 characters
address_line1
string | null
<= 255 characters
address_line2
string | null
<= 255 characters
city
string | null
<= 255 characters
country
string | null
<= 255 characters
postcode
string | null
<= 50 characters
social_links
One of:

Handles only, without URLs — the frontend adds the domains.

object
instagram
string
tiktok
string
facebook
string
x
string
preferences

The client’s interests. They can be listed explicitly, derived from the selected unit with the based_on_unit flag, or both: values that are sent are kept, and the unit’s data is added to them.

object
based_on_unit

“Based on the selected unit”: adds the project’s property type and area, the unit’s tags, the project’s features and amenities, and the unit price as both budget bounds. The budget is the only field that is not merged: an explicitly provided bound takes precedence, and the unit price fills only a missing bound, and only if doing so does not invert the range.

boolean
property_types

Property types of interest (Project.type values). Stored on the client.

Array<integer>
<= 100 items
preferred_area_ids

Area IDs from GET /areas. An unknown ID results in 400.

Array<integer>
<= 100 items
property_features

Free-text tags on the client profile. Trimmed; duplicates are removed case-insensitively.

Array<string>
<= 100 items
preference_ids

Unit tag IDs from GET /preferences, linked to the opportunity.

Array<integer>
<= 100 items
project_preference_ids

Feature/amenity IDs from GET /project-preferences; both sections in a single list.

Array<integer>
<= 100 items
investment_type

Free-form integer

integer | null
min_budget
number | null format: double
max_budget
number | null format: double
message

The client’s own message. Stored in the opportunity notes; the client’s notes are left unchanged, as that field is reserved for the agent.

string | null
<= 5000 characters
language

Offer language, converted to lower case.

string | null
default: en <= 10 characters
currency

Currency of both the offer and the opportunity budget, so that the figures on the card and in the offer are in the same currency. Defaults to the project currency if it is AED or USD; otherwise AED.

string | null
Allowed values: AED USD

Examples

Minimal request — everything else is derived from the unit

{
"client": {
"first_name": "Ivan",
"last_name": "Petrov",
"email": "ivan.petrov@example.com",
"mobile_number": "+971 50 111 22 33"
},
"preferences": {
"based_on_unit": true
},
"message": "Please call me after 6 pm"
}

The application has been accepted.

Media typeapplication/json
object
status
required
string
Allowed value: success
data
required

The shape depends on the operation.

data
required
object
client
object
id
integer
first_name
string
last_name
string | null
email
string | null
mobile_number
string | null
property_types
Array<integer>
preferred_area_ids
Array<integer>
property_features
Array<string>
created

false means the application was attached to an existing client of the company. That client’s fields were not changed, and the arrays above show what is stored on the record rather than what this application derived.

boolean
created_at
string format: date-time
opportunity

The client’s interests — a card on the deals board.

object
id
integer
name

Generated as <client> — <project>, unit <number>, up to 255 characters.

string
stage
object
id
integer
name
string
position

Column order on the board.

integer
investment_type
integer | null
currency
string
min_budget
number | null format: double
max_budget
number | null format: double
position

The card is placed at the end of its column.

integer
preferences
Array<object>
object
id
integer
name
string
project_preferences
object
features
Array<object>
object
id
integer
name
string
type

1 — feature, 2 — amenity.

integer
Allowed values: 1 2
amenities
Array<object>
object
id
integer
name
string
type

1 — feature, 2 — amenity.

integer
Allowed values: 1 2
created_at
string format: date-time
offer

A draft offer. The PDF is generated and sent by the main backend.

object
id
integer
uuid

Public identifier of the offer, used to build the client-facing link.

string format: uuid
offer_status
object
id
integer
name
string
unit
object
id
integer
unit_number
string
project_id
integer
project_name
string
price

In the key’s prices.

number format: double
language
string
currency
string
measure_units

1 — ft²

integer
Allowed values: 1 2
advisor_name
string
advisor_email
string | null
advisor_phone
string | null
is_sent

Always false here.

boolean
created_at
string format: date-time

Example

{
"status": "success",
"data": {
"opportunity": {
"stage": {
"name": "New Inquiry"
},
"project_preferences": {
"features": [
{
"name": "Infinity Pool",
"type": 1
}
],
"amenities": [
{
"name": "Infinity Pool",
"type": 1
}
]
}
},
"offer": {
"measure_units": 1
}
}
}

The request body failed validation: it is not JSON or exceeds 64 KiB, the first name is missing, neither an email address nor a mobile number is provided, a reference ID is unknown, the budget range is inverted, or a value exceeds its maximum length.

Media typeapplication/json
object
status
required
string
Allowed value: error
message
required

Human-readable message.

string

Examples

ExamplenoContact
{
"status": "error",
"message": "client.email or client.mobile_number is required"
}

The key is missing or invalid, or its company or author has been removed (api key author is gone).

Media typeapplication/json
object
status
required
string
Allowed value: error
message
required

Human-readable message.

string

Example

{
"status": "error"
}

The key is valid, but it is restricted to a list of domains and the request’s Origin (or Referer) does not match any of them; or the key’s agency has no approved licence.

Media typeapplication/json
object
status
required
string
Allowed value: error
message
required

Human-readable message.

string

Examples

{
"status": "error",
"message": "origin is not allowed for this api key"
}

No record exists with the given ID — or it belongs to a developer the key’s agency has no approved contract with.

Media typeapplication/json
object
status
required
string
Allowed value: error
message
required

Human-readable message.

string

Examples

Exampleunit
{
"status": "error",
"message": "unit not found"
}

An internal server error, or offer status catalogue is empty — the offer status reference data has not been configured. This cannot be resolved by the caller.

Media typeapplication/json
object
status
required
string
Allowed value: error
message
required

Human-readable message.

string

Example

{
"status": "error"
}