Skip to main content

Reservations Management

Create or update unit reservations in Sakneen from an external system using external record identifiers.

Overview

The Reservations API accepts reservation data from partner systems and queues it for asynchronous processing. A successful request means Sakneen accepted the payload for processing; the actual create/update work runs after the request through the Google Cloud Tasks-backed queue ledger.

Use this endpoint when your external system owns the reservation lifecycle and needs to send reservation records into Sakneen.

Reservations vs. Reservations Webhook

Use POST /external/apis/v1.0/reservations to create or update reservation records from your external system.

Use POST /external/apis/v1.0/reservations/webhook for callback flows that update payment, cheque, or status information for existing Sakneen reservations.

Endpoint

POST /external/apis/v1.0/reservations

Authentication

All requests require a valid API key. The API key must be configured with allowExternal: true permission to access this endpoint.

Include your API key as the api-key request header:

api-key: YOUR_API_KEY

Headers

HeaderRequiredDescription
api-keyYesYour organization's API key
Content-TypeYesMust be application/json
languageOptionalLanguage preference, for example en-us or ar-eg

Request Body Schema

Required Fields

FieldTypeDescriptionValidation
recordSystemIdstringUnique reservation identifier from your external system. Sakneen uses this value to create or update the reservation.Required, non-empty string
unitRecordSystemIdstringExternal unit identifier. Sakneen resolves this against the unit's recordSystemId.Required, non-empty string
statusenumReservation status to store in Sakneen.Required, one of the status values below

Optional Fields

FieldTypeDescriptionValidation
fullNamestringCustomer full nameOptional, non-empty string
phoneNumberstringCustomer phone numberOptional, non-empty string
emailstringCustomer email addressOptional, valid email
nationalIDstringCustomer national IDOptional, non-empty string
crmIDstringExternal CRM identifierOptional, non-empty string
reservationAmountnumberReservation amountOptional, must be 0 or greater
expiryDatestringReservation expiry dateOptional, ISO 8601 date string
crmOpportunityIdstringExternal CRM opportunity identifierOptional, non-empty string
salesPersonEmailstringEmail address of the Sakneen sales person to attach to the reservationOptional, valid email
reservationDataobjectAdditional reservation fields to map into the organization's reservation form when configuredOptional object

Enum: status

ValueMeaning
pendingReservation is pending
approvedReservation is approved
rejectedReservation is rejected
canceledReservation is canceled
expiredReservation is expired

Matching Behavior

  • recordSystemId is the external reservation ID and is used as the upsert key.
  • If a non-deleted reservation already exists with the same recordSystemId for your organization, Sakneen updates it.
  • If no matching reservation exists, Sakneen creates a new reservation and assigns a Sakneen serial number.
  • unitRecordSystemId is matched against the unit's external recordSystemId.
  • If the unit cannot be resolved during processing, Sakneen still creates or updates the reservation and stores the original unitRecordSystemId in the raw reservation payload. The reservation will not be linked to a local unit until the unit exists and the reservation is sent again.
  • This endpoint does not update unit data or unit status. Send unit changes through POST /external/apis/v1.0/units.

Async Processing

A successful request returns 201 Created with { "ok": true }. This means the request was accepted and queued.

Reservation processing happens asynchronously. If the queued job cannot resolve unitRecordSystemId to a local unit, Sakneen still creates or updates the reservation without a local unit link and records an operational warning. Other processing failures are tracked internally by Sakneen's Google Cloud Tasks-backed queue ledger and operational notifications; those failures are not returned to the original HTTP request.

Request Example

{
"recordSystemId": "RES-001",
"unitRecordSystemId": "UNIT-001",
"status": "approved",
"fullName": "Palm Hills Client",
"phoneNumber": "+201001234567",
"email": "[email protected]",
"nationalID": "29801011234567",
"crmID": "CRM-123",
"reservationAmount": 150000,
"expiryDate": "2026-07-01T00:00:00.000Z",
"crmOpportunityId": "OPP-456",
"salesPersonEmail": "[email protected]",
"reservationData": {
"paymentPlan": "5 years",
"source": "external-crm"
}
}

Success Response

201 Created

{
"ok": true
}

Error Responses

400 Bad Request

{
"message": [
"recordSystemId should not be empty",
"unitRecordSystemId should not be empty",
"status must be one of the following values: pending, approved, rejected, canceled, expired"
],
"error": "Bad Request",
"statusCode": 400
}

401 Unauthorized

{
"statusCode": 401,
"message": "Access Denied, API key not valid"
}

Code Examples

cURL

curl -X POST "https://your-domain/external/apis/v1.0/reservations" \
-H "api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "language: en-us" \
-d '{
"recordSystemId": "RES-001",
"unitRecordSystemId": "UNIT-001",
"status": "approved",
"fullName": "Palm Hills Client",
"phoneNumber": "+201001234567",
"email": "[email protected]",
"reservationAmount": 150000
}'

JavaScript/Node.js

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

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

if (!response.ok) {
const errorBody = await response.json().catch(() => ({}));
throw new Error(
`Reservation request failed (${response.status}): ${JSON.stringify(errorBody)}`,
);
}

return response.json();
}

await upsertReservation({
recordSystemId: 'RES-001',
unitRecordSystemId: 'UNIT-001',
status: 'approved',
fullName: 'Palm Hills Client',
phoneNumber: '+201001234567',
email: '[email protected]',
reservationAmount: 150000,
});

Python

import os
import requests

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

def upsert_reservation(reservation_data):
response = requests.post(
f'https://{base_domain}/external/apis/v1.0/reservations',
json=reservation_data,
headers={
'api-key': api_key,
'Content-Type': 'application/json',
'language': 'en-us',
},
)

response.raise_for_status()
return response.json()

upsert_reservation({
'recordSystemId': 'RES-001',
'unitRecordSystemId': 'UNIT-001',
'status': 'approved',
'fullName': 'Palm Hills Client',
'phoneNumber': '+201001234567',
'email': '[email protected]',
'reservationAmount': 150000,
})

Best Practices

  • Send units first through POST /external/apis/v1.0/units, then send reservations that reference those units by unitRecordSystemId.
  • If a reservation is accepted before its unit exists in Sakneen, send the unit through POST /external/apis/v1.0/units and then resend the reservation to link it.
  • Keep recordSystemId stable for the lifetime of the reservation in your external system.
  • Retry only the initial HTTP request if Sakneen does not return a 201 response.
  • Treat { "ok": true } as acceptance into the queue, not confirmation that the reservation has already been written.