Skip to main content

Unit Management

Manage property units in the Sakneen system using the Units API.

Overview

The Units API allows you to create, update, and retrieve property units in the Sakneen system. This is essential for property developers, real estate companies, and property management systems that need to maintain up-to-date unit inventory and display unit data on external platforms.


List Units

Retrieve a paginated list of units belonging to your organization.

Endpoint

GET /external/apis/v1.0/units

Authentication

All requests require a valid API key in the header:

api-key: YOUR_API_KEY

Query Parameters

ParameterTypeDefaultDescription
pagenumber1Page number (min: 1)
limitnumber10Number of results per page (min: 1, max: 100)
sortBystringdefaultSortField to sort by (defaults to createdAt)
orderstringdescSort order: asc, desc, ascending, or descending
statusesstring[]Filter by unit statuses (e.g., available, sold). Pass multiple values as repeated query params or a comma-separated list
Hard Limit

The maximum number of records per page is 100. Even if a higher limit value is provided, the API will cap it at 100.

Response Fields

Each unit object in the response includes:

FieldTypeDescription
_idstringMongoDB document ID
recordSystemIdstringUnique system identifier for the record
unitIdstringUnique identifier for the unit
titlestringUnit title
translationsobjectLocalized data including name and slug per language (en-us, ar-eg)
statusstringCurrent status of the unit
saleTypestringType of sale (primary or resale)
propertyTypeobjectPopulated property type details
pricenumberBase price of the unit
priceCurrencystringCurrency code for the price
buanumberBuilt-up area in square meters
totalBuanumberTotal built-up area in square meters
landAreanumberLand area in square meters
gardenAreanumberGarden area in square meters
bedsnumberNumber of bedrooms
bathroomsnumberNumber of bathrooms
floorstringFloor level
finishingTypeobjectPopulated finishing type details
amenitiesarrayPopulated list of amenities
featuresarrayPopulated list of features
mainImageUrlstringURL of the unit's main image
mediaarrayArray of media objects (images, videos, etc.)
deliveryDetailsobjectDelivery date and timeline information
createdAtstringISO 8601 timestamp of creation
updatedAtstringISO 8601 timestamp of last update

Success Response (200 OK)

{
"data": [
{
"_id": "64f1234567890abcdef123456",
"recordSystemId": "00163EAB688B1EECA9C3E1F963E029CC",
"unitId": "VH-12",
"title": "Luxury Apartment - VH-12",
"translations": {
"en-us": {
"name": "Luxury Apartment VH-12",
"slug": "luxury-apartment-vh-12"
},
"ar-eg": {
"name": "شقة فاخرة VH-12",
"slug": "شقة-فاخرة-vh-12"
}
},
"status": "available",
"saleType": "primary",
"propertyType": {
"_id": "64a1b2c3d4e5f6789012345",
"name": "Apartment"
},
"price": 1200000,
"priceCurrency": "EGP",
"bua": 120,
"totalBua": 150,
"landArea": 150,
"gardenArea": 30,
"beds": 2,
"bathrooms": 2,
"floor": "2nd",
"finishingType": {
"_id": "64a1b2c3d4e5f6789012346",
"name": "Fully Finished"
},
"amenities": [
{ "_id": "64a1b2c3d4e5f6789012347", "name": "Swimming Pool" },
{ "_id": "64a1b2c3d4e5f6789012348", "name": "Gym" }
],
"features": [
{ "_id": "64a1b2c3d4e5f6789012349", "name": "Balcony" },
{ "_id": "64a1b2c3d4e5f6789012350", "name": "City View" }
],
"mainImageUrl": "https://example.com/images/vh-12-main.jpg",
"media": [
{ "url": "https://example.com/images/vh-12-1.jpg", "type": "image" },
{ "url": "https://example.com/images/vh-12-2.jpg", "type": "image" }
],
"deliveryDetails": {
"type": "months",
"date": "2026-12-01",
"months": 18
},
"createdAt": "2024-08-07T10:30:00.000Z",
"updatedAt": "2024-08-10T14:15:00.000Z"
}
],
"pagination": {
"count": 1,
"limit": 10,
"pages": 1,
"total": 1
}
}

Error Responses

401 Unauthorized - Missing or Invalid API Key

{
"statusCode": 401,
"message": "Access Denied, API key not provided",
"error": "Unauthorized"
}

Code Examples

cURL

# Basic request
curl -X GET "https://your-domain/external/apis/v1.0/units?page=1&limit=20" \
-H "api-key: YOUR_API_KEY"

# With sorting and status filter
curl -X GET "https://your-domain/external/apis/v1.0/units?page=1&limit=10&sortBy=price&order=asc&statuses=available&statuses=reserved" \
-H "api-key: YOUR_API_KEY"

JavaScript/Node.js

const apiKey = process.env.SAKNEEN_API_KEY;
const baseDomain = process.env.SAKNEEN_DOMAIN;

async function getUnits({ page = 1, limit = 10, sortBy, order, statuses } = {}) {
const params = new URLSearchParams({ page, limit });

if (sortBy) params.append('sortBy', sortBy);
if (order) params.append('order', order);
if (statuses) {
for (const status of statuses) {
params.append('statuses', status);
}
}

const response = await fetch(
`https://${baseDomain}/external/apis/v1.0/units?${params}`,
{
method: 'GET',
headers: { 'api-key': apiKey },
}
);

if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(`HTTP ${response.status}: ${errorData.message || response.statusText}`);
}

return await response.json();
}

// Usage
getUnits({ page: 1, limit: 20, statuses: ['available'] })
.then(result => {
console.log(`Found ${result.pagination.total} units`);
console.log('Units:', result.data);
})
.catch(error => console.error('Error:', error));

Python

import os
import requests

api_key = os.getenv('SAKNEEN_API_KEY')
base_domain = os.getenv('SAKNEEN_DOMAIN')

def get_units(page=1, limit=10, sort_by=None, order=None, statuses=None):
headers = { 'api-key': api_key }
params = { 'page': page, 'limit': limit }

if sort_by:
params['sortBy'] = sort_by
if order:
params['order'] = order
if statuses:
params['statuses'] = statuses # requests handles list params

response = requests.get(
f'https://{base_domain}/external/apis/v1.0/units',
headers=headers,
params=params
)
response.raise_for_status()
return response.json()

# Usage
try:
result = get_units(page=1, limit=20, statuses=['available', 'reserved'])
print(f"Found {result['pagination']['total']} units")
for unit in result['data']:
print(f" - {unit['unitId']}: {unit['title']} ({unit['status']})")
except requests.exceptions.RequestException as e:
print(f'Error fetching units: {e}')

PHP

<?php
function getUnits($page = 1, $limit = 10, $sortBy = null, $order = null, $statuses = []) {
$domain = $_ENV['SAKNEEN_DOMAIN'];
$apiKey = $_ENV['SAKNEEN_API_KEY'];

$params = ['page' => $page, 'limit' => $limit];
if ($sortBy) $params['sortBy'] = $sortBy;
if ($order) $params['order'] = $order;

$query = http_build_query($params);
foreach ($statuses as $status) {
$query .= '&' . http_build_query(['statuses' => $status]);
}

$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://{$domain}/external/apis/v1.0/units?{$query}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['api-key: ' . $apiKey],
]);

$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);

if ($httpCode !== 200) {
throw new Exception("HTTP Error: $httpCode");
}

return json_decode($response, true);
}

// Usage
try {
$result = getUnits(1, 20, null, null, ['available']);
echo "Found {$result['pagination']['total']} units\n";
foreach ($result['data'] as $unit) {
echo " - {$unit['unitId']}: {$unit['title']} ({$unit['status']})\n";
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
?>

Get Unit by Slug

Retrieve a single unit by its slug. The slug is matched against all available languages (en-us and ar-eg).

Endpoint

GET /external/apis/v1.0/units/:slug

Authentication

All requests require a valid API key in the header:

api-key: YOUR_API_KEY

Path Parameters

ParameterTypeDescription
slugstringThe unit slug (from any language in translations)

Response

The response contains a single unit object with the exact same fields as each item in the List Units response.

Success Response (200 OK)

{
"data": {
"_id": "64f1234567890abcdef123456",
"recordSystemId": "00163EAB688B1EECA9C3E1F963E029CC",
"unitId": "VH-12",
"title": "Luxury Apartment - VH-12",
"translations": {
"en-us": {
"name": "Luxury Apartment VH-12",
"slug": "luxury-apartment-vh-12"
},
"ar-eg": {
"name": "شقة فاخرة VH-12",
"slug": "شقة-فاخرة-vh-12"
}
},
"status": "available",
"saleType": "primary",
"propertyType": {
"_id": "64a1b2c3d4e5f6789012345",
"name": "Apartment"
},
"price": 1200000,
"priceCurrency": "EGP",
"bua": 120,
"totalBua": 150,
"landArea": 150,
"gardenArea": 30,
"beds": 2,
"bathrooms": 2,
"floor": "2nd",
"finishingType": {
"_id": "64a1b2c3d4e5f6789012346",
"name": "Fully Finished"
},
"amenities": [
{ "_id": "64a1b2c3d4e5f6789012347", "name": "Swimming Pool" },
{ "_id": "64a1b2c3d4e5f6789012348", "name": "Gym" }
],
"features": [
{ "_id": "64a1b2c3d4e5f6789012349", "name": "Balcony" },
{ "_id": "64a1b2c3d4e5f6789012350", "name": "City View" }
],
"mainImageUrl": "https://example.com/images/vh-12-main.jpg",
"media": [
{ "url": "https://example.com/images/vh-12-1.jpg", "type": "image" },
{ "url": "https://example.com/images/vh-12-2.jpg", "type": "image" }
],
"deliveryDetails": {
"type": "months",
"date": "2026-12-01",
"months": 18
},
"createdAt": "2024-08-07T10:30:00.000Z",
"updatedAt": "2024-08-10T14:15:00.000Z"
}
}

Error Responses

404 Not Found - Unit does not exist

{
"statusCode": 404,
"message": "There is no unit with slug: non-existent-slug",
"error": "Not Found"
}

401 Unauthorized - Missing or Invalid API Key

{
"statusCode": 401,
"message": "Access Denied, API key not provided",
"error": "Unauthorized"
}

Code Examples

cURL

curl -X GET "https://your-domain/external/apis/v1.0/units/luxury-apartment-vh-12" \
-H "api-key: YOUR_API_KEY"

JavaScript/Node.js

const apiKey = process.env.SAKNEEN_API_KEY;
const baseDomain = process.env.SAKNEEN_DOMAIN;

async function getUnitBySlug(slug) {
const response = await fetch(
`https://${baseDomain}/external/apis/v1.0/units/${encodeURIComponent(slug)}`,
{
method: 'GET',
headers: { 'api-key': apiKey },
}
);

if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(`HTTP ${response.status}: ${errorData.message || response.statusText}`);
}

return await response.json();
}

// Usage
getUnitBySlug('luxury-apartment-vh-12')
.then(result => console.log('Unit:', result.data))
.catch(error => console.error('Error:', error));

Python

import os
import requests
from urllib.parse import quote

api_key = os.getenv('SAKNEEN_API_KEY')
base_domain = os.getenv('SAKNEEN_DOMAIN')

def get_unit_by_slug(slug):
headers = { 'api-key': api_key }

response = requests.get(
f'https://{base_domain}/external/apis/v1.0/units/{quote(slug)}',
headers=headers
)
response.raise_for_status()
return response.json()

# Usage
try:
result = get_unit_by_slug('luxury-apartment-vh-12')
unit = result['data']
print(f"Unit: {unit['unitId']} - {unit['title']}")
print(f"Price: {unit['price']} {unit['priceCurrency']}")
print(f"Status: {unit['status']}")
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
print('Unit not found')
else:
print(f'Error: {e}')

PHP

<?php
function getUnitBySlug($slug) {
$domain = $_ENV['SAKNEEN_DOMAIN'];
$apiKey = $_ENV['SAKNEEN_API_KEY'];

$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://{$domain}/external/apis/v1.0/units/" . urlencode($slug),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['api-key: ' . $apiKey],
]);

$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);

if ($httpCode === 404) {
throw new Exception("Unit not found");
}
if ($httpCode !== 200) {
throw new Exception("HTTP Error: $httpCode");
}

return json_decode($response, true);
}

// Usage
try {
$result = getUnitBySlug('luxury-apartment-vh-12');
$unit = $result['data'];
echo "Unit: {$unit['unitId']} - {$unit['title']}\n";
echo "Price: {$unit['price']} {$unit['priceCurrency']}\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
?>

Create or Update Unit

Create a new unit or update an existing one in the Sakneen system.

Endpoint

POST /external/apis/v1.0/units

Authentication

All requests require a valid API key in the header:

api-key: YOUR_API_KEY

Request Body Schema

Required Fields

FieldTypeDescription
recordSystemIdstringUnique system identifier for the record
unitIdstringUnique identifier for the unit

Optional Fields

FieldTypeDescriptionValidation
pricenumberBase price of the unit>= 0
buanumberBuilt-up area in square meters>= 0
propertyTypestringType of propertyAny string
statusstringCurrent status of the unitAny string
bedroomsnumberNumber of bedrooms>= 0
bathroomsnumberNumber of bathrooms>= 0
landAreanumberLand area in square meters>= 0
gardenAreanumberGarden area in square meters>= 0
finishingTypestringType of finishingAny string
floorstringFloor levelAny string
amenitiesstring[]Array of amenity namesArray of strings
featuresstring[]Array of feature namesArray of strings
saleTypestringType of saleprimary or resale (default: primary)
priceByYearobjectPrice variations by yearKey-value pairs (year: price)
priceByPaymentPlanarrayArray of payment plan pricingSee Payment Plan Schema below

Payment Plan Schema

Each item in priceByPaymentPlan array should contain:

FieldTypeRequiredDescriptionValidation
pricenumberYesPrice for this payment plan>= 0
paymentPlanIdstringYesMongoDB ObjectId of payment planValid MongoDB ObjectId
finishingTypeIdstringYesMongoDB ObjectId of finishing typeValid MongoDB ObjectId
numberOfYearsnumberOptionalPayment plan duration in years>= 0

Headers

HeaderRequiredDescription
api-keyYesYour organization's API key
Content-TypeYesMust be application/json
languageOptionalLanguage preference (e.g., en-us, ar-eg)

Complete Example Request

{
"recordSystemId": "00163EAB688B1EECA9C3E1F963E029CC",
"unitId": "VH-12",
"price": 1200000,
"bua": 120,
"propertyType": "apartment",
"status": "available",
"bedrooms": 2,
"bathrooms": 2,
"landArea": 150,
"gardenArea": 30,
"finishingType": "Fully finished",
"floor": "2nd",
"amenities": ["wifi", "parking", "gym", "pool"],
"features": ["balcony", "parking", "city_view"],
"saleType": "primary",
"priceByYear": {
"2024": 1200000,
"2025": 1250000,
"2026": 1300000
},
"priceByPaymentPlan": [
{
"price": 1200000,
"paymentPlanId": "64a1b2c3d4e5f6789012345",
"finishingTypeId": "64a1b2c3d4e5f6789012346",
"numberOfYears": 5
},
{
"price": 1150000,
"paymentPlanId": "64a1b2c3d4e5f6789012347",
"finishingTypeId": "64a1b2c3d4e5f6789012348",
"numberOfYears": 3
}
]
}

Success Response (201 Created)

{
"success": true,
"message": "Unit created/updated successfully",
"data": {
"unitId": "VH-12",
"recordSystemId": "00163EAB688B1EECA9C3E1F963E029CC"
}
}

Error Responses

400 Bad Request - Validation Error

{
"statusCode": 400,
"message": [
"recordSystemId should not be empty",
"unitId should not be empty",
"price must be a number conforming to the specified constraints"
],
"error": "Bad Request"
}

401 Unauthorized - Invalid API Key

{
"statusCode": 401,
"message": "Access Denied, API key not provided",
"error": "Unauthorized"
}

Code Examples

cURL

curl -X POST "https://your-domain/external/apis/v1.0/units" \
-H "api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "language: en-us" \
-d '{
"recordSystemId": "00163EAB688B1EECA9C3E1F963E029CC",
"unitId": "VH-12",
"price": 1200000,
"bua": 120,
"propertyType": "apartment",
"status": "available",
"bedrooms": 2,
"bathrooms": 2,
"landArea": 150,
"finishingType": "Fully finished",
"floor": "2nd",
"amenities": ["wifi", "parking", "gym"],
"features": ["balcony", "parking"],
"saleType": "primary"
}'

JavaScript/Node.js

const apiKey = process.env.SAKNEEN_API_KEY;
const baseDomain = process.env.SAKNEEN_DOMAIN;

async function createUnit(unitData) {
try {
const response = await fetch(`https://${baseDomain}/external/apis/v1.0/units`, {
method: 'POST',
headers: {
'api-key': apiKey,
'Content-Type': 'application/json',
'language': 'en-us'
},
body: JSON.stringify(unitData)
});

if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(`HTTP ${response.status}: ${errorData.message || response.statusText}`);
}

const result = await response.json();
console.log('Unit created:', result);
return result;
} catch (error) {
console.error('Error creating unit:', error);
throw error;
}
}

// Usage
const unitData = {
recordSystemId: "00163EAB688B1EECA9C3E1F963E029CC",
unitId: "VH-12",
price: 1200000,
bua: 120,
propertyType: "apartment",
status: "available",
bedrooms: 2,
bathrooms: 2,
landArea: 150,
gardenArea: 30,
finishingType: "Fully finished",
floor: "2nd",
amenities: ["wifi", "parking", "gym", "pool"],
features: ["balcony", "parking", "city_view"],
saleType: "primary"
};

createUnit(unitData)
.then(result => console.log('Success:', result))
.catch(error => console.error('Error:', error));

Python

import requests
import os

def create_unit(unit_data):
api_key = os.getenv('SAKNEEN_API_KEY')
base_domain = os.getenv('SAKNEEN_DOMAIN')

url = f'https://{base_domain}/external/apis/v1.0/units'
headers = {
'api-key': api_key,
'Content-Type': 'application/json',
'language': 'en-us'
}

try:
response = requests.post(url, headers=headers, json=unit_data)
response.raise_for_status()

result = response.json()
print(f'Unit created: {result}')
return result
except requests.exceptions.RequestException as e:
print(f'Error creating unit: {e}')
raise

# Usage
unit_data = {
"recordSystemId": "00163EAB688B1EECA9C3E1F963E029CC",
"unitId": "VH-12",
"price": 1200000,
"bua": 120,
"propertyType": "apartment",
"status": "available",
"bedrooms": 2,
"bathrooms": 2,
"landArea": 150,
"gardenArea": 30,
"finishingType": "Fully finished",
"floor": "2nd",
"amenities": ["wifi", "parking", "gym", "pool"],
"features": ["balcony", "parking", "city_view"],
"saleType": "primary"
}

try:
result = create_unit(unit_data)
print(f'Success: {result}')
except Exception as e:
print(f'Error: {e}')

PHP

<?php
function createUnit($unitData) {
$domain = $_ENV['SAKNEEN_DOMAIN'];
$apiKey = $_ENV['SAKNEEN_API_KEY'];

$headers = [
'api-key: ' . $apiKey,
'Content-Type: application/json'
];

$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://{$domain}/external/apis/v1.0/units",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($unitData),
CURLOPT_HTTPHEADER => $headers,
]);

$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);

if ($httpCode !== 201) {
throw new Exception("HTTP Error: $httpCode");
}

return json_decode($response, true);
}

// Usage
$unitData = [
'recordSystemId' => '00163EAB688B1EECA9C3E1F963E029CC',
'unitId' => 'VH-12',
'price' => 1200000,
'bua' => 120,
'propertyType' => 'apartment',
'status' => 'available',
'bedrooms' => 2,
'bathrooms' => 2,
'landArea' => 150,
'finishingType' => 'Fully finished',
'floor' => '2nd',
'amenities' => ['wifi', 'parking'],
'features' => ['parking'],
'saleType' => 'primary'
];

try {
$result = createUnit($unitData);
echo "Unit created: " . json_encode($result) . "\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
?>

Validation Rules

Required Field Validation

  • All required fields must be present and not empty
  • price must be a positive number
  • propertyType must be one of the allowed values
  • status must be one of the allowed values

Data Type Validation

  • bedrooms and bathrooms must be integers if provided
  • bua and landArea must be numbers if provided
  • amenities and features must be arrays if provided
  • deliveryDate must follow DD-MM-YYYY format if provided

Business Logic Validation

  • If deliveryDateType is "months", deliveryDateMonths should be provided
  • If deliveryDateType is "date", deliveryDate should be provided
  • unitId should be unique within the system

Error Handling

Common Error Scenarios

  1. Missing API Key (401)

    • Ensure API key is included in headers
    • Verify API key is valid
  2. Invalid Data (400)

    • Check all required fields are present
    • Verify data types match the specification
    • Ensure enum values are from allowed lists
  3. Unit Not Found (404) (GET by slug only)

    • Verify the slug exists and belongs to your organization
    • Check both en-us and ar-eg slugs
  4. Network Issues

    • Implement retry logic for transient failures
    • Set appropriate timeouts

Best Practices

  1. Pagination: Use reasonable page sizes (10-50) to balance performance and usability. Avoid requesting the maximum limit of 100 unless necessary.
  2. Filtering: Use the statuses filter to retrieve only the units you need, reducing response size and processing time.
  3. Slug Lookup: Cache unit slugs client-side to avoid unnecessary list requests when you need a specific unit.
  4. Data Validation: Always validate data before sending to the POST API.
  5. Error Handling: Implement comprehensive error handling for all endpoints.
  6. Retry Logic: Add retry mechanisms for network failures.
  7. Rate Limiting: Respect API rate limits.

Use Cases

Property Listing Websites

  • Use List Units to display paginated property listings with status filtering
  • Use Get Unit by Slug for individual property detail pages with SEO-friendly URLs
  • Use Create or Update Unit to sync inventory from your property management system

Real Estate Portals

  • Import and browse property inventory using the list endpoint
  • Link to individual unit pages using slugs
  • Keep unit data synchronized with bulk create/update operations

CRM Integration

  • Retrieve available units for sales teams
  • Display unit details in customer-facing views
  • Track unit availability changes via status filtering