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
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number (min: 1) |
limit | number | 10 | Number of results per page (min: 1, max: 100) |
sortBy | string | defaultSort | Field to sort by (defaults to createdAt) |
order | string | desc | Sort order: asc, desc, ascending, or descending |
statuses | string[] | — | Filter by unit statuses (e.g., available, sold). Pass multiple values as repeated query params or a comma-separated list |
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:
| Field | Type | Description |
|---|---|---|
_id | string | MongoDB document ID |
recordSystemId | string | Unique system identifier for the record |
unitId | string | Unique identifier for the unit |
title | string | Unit title |
translations | object | Localized data including name and slug per language (en-us, ar-eg) |
status | string | Current status of the unit |
saleType | string | Type of sale (primary or resale) |
propertyType | object | Populated property type details |
price | number | Base price of the unit |
priceCurrency | string | Currency code for the price |
bua | number | Built-up area in square meters |
totalBua | number | Total built-up area in square meters |
landArea | number | Land area in square meters |
gardenArea | number | Garden area in square meters |
beds | number | Number of bedrooms |
bathrooms | number | Number of bathrooms |
floor | string | Floor level |
finishingType | object | Populated finishing type details |
amenities | array | Populated list of amenities |
features | array | Populated list of features |
mainImageUrl | string | URL of the unit's main image |
media | array | Array of media objects (images, videos, etc.) |
deliveryDetails | object | Delivery date and timeline information |
createdAt | string | ISO 8601 timestamp of creation |
updatedAt | string | ISO 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
| Parameter | Type | Description |
|---|---|---|
slug | string | The 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
| Field | Type | Description |
|---|---|---|
recordSystemId | string | Unique system identifier for the record |
unitId | string | Unique identifier for the unit |
Optional Fields
| Field | Type | Description | Validation |
|---|---|---|---|
price | number | Base price of the unit | >= 0 |
bua | number | Built-up area in square meters | >= 0 |
propertyType | string | Type of property | Any string |
status | string | Current status of the unit | Any string |
bedrooms | number | Number of bedrooms | >= 0 |
bathrooms | number | Number of bathrooms | >= 0 |
landArea | number | Land area in square meters | >= 0 |
gardenArea | number | Garden area in square meters | >= 0 |
finishingType | string | Type of finishing | Any string |
floor | string | Floor level | Any string |
amenities | string[] | Array of amenity names | Array of strings |
features | string[] | Array of feature names | Array of strings |
saleType | string | Type of sale | primary or resale (default: primary) |
priceByYear | object | Price variations by year | Key-value pairs (year: price) |
priceByPaymentPlan | array | Array of payment plan pricing | See Payment Plan Schema below |
Payment Plan Schema
Each item in priceByPaymentPlan array should contain:
| Field | Type | Required | Description | Validation |
|---|---|---|---|---|
price | number | Yes | Price for this payment plan | >= 0 |
paymentPlanId | string | Yes | MongoDB ObjectId of payment plan | Valid MongoDB ObjectId |
finishingTypeId | string | Yes | MongoDB ObjectId of finishing type | Valid MongoDB ObjectId |
numberOfYears | number | Optional | Payment plan duration in years | >= 0 |
Headers
| Header | Required | Description |
|---|---|---|
api-key | Yes | Your organization's API key |
Content-Type | Yes | Must be application/json |
language | Optional | Language 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
pricemust be a positive numberpropertyTypemust be one of the allowed valuesstatusmust be one of the allowed values
Data Type Validation
bedroomsandbathroomsmust be integers if providedbuaandlandAreamust be numbers if providedamenitiesandfeaturesmust be arrays if provideddeliveryDatemust follow DD-MM-YYYY format if provided
Business Logic Validation
- If
deliveryDateTypeis "months",deliveryDateMonthsshould be provided - If
deliveryDateTypeis "date",deliveryDateshould be provided unitIdshould be unique within the system
Error Handling
Common Error Scenarios
-
Missing API Key (401)
- Ensure API key is included in headers
- Verify API key is valid
-
Invalid Data (400)
- Check all required fields are present
- Verify data types match the specification
- Ensure enum values are from allowed lists
-
Unit Not Found (404) (GET by slug only)
- Verify the slug exists and belongs to your organization
- Check both
en-usandar-egslugs
-
Network Issues
- Implement retry logic for transient failures
- Set appropriate timeouts
Best Practices
- Pagination: Use reasonable page sizes (10-50) to balance performance and usability. Avoid requesting the maximum limit of 100 unless necessary.
- Filtering: Use the
statusesfilter to retrieve only the units you need, reducing response size and processing time. - Slug Lookup: Cache unit slugs client-side to avoid unnecessary list requests when you need a specific unit.
- Data Validation: Always validate data before sending to the POST API.
- Error Handling: Implement comprehensive error handling for all endpoints.
- Retry Logic: Add retry mechanisms for network failures.
- 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