Environment Setup
All examples use the Sandbox environment by default. Set the following environment variables:CLEANLIFE_API_KEY=pk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
CLEANLIFE_BASE_URL=https://apiv3.thecleanlife.dev/v1
CLEANLIFE_BASE_URL to https://api.cleanlife.sa/v1 and use your production API key.
cURL
Find or Create Contact
curl -X POST "${CLEANLIFE_BASE_URL}/partners/contacts" \
-H "x-api-key: ${CLEANLIFE_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"phone": "+966500000000",
"name": "Ahmed Ali",
"latitude": "24.7136",
"longitude": "46.6753",
"cityName": "Riyadh",
"districtName": "Al Olaya",
"streetName": "King Fahd Road"
}'
List Services
curl -X GET "${CLEANLIFE_BASE_URL}/partners/services?addressId=bbbbbbbb-0000-0000-0000-000000000002&page=1&limit=50" \
-H "x-api-key: ${CLEANLIFE_API_KEY}"
Create a Booking
curl -X POST "${CLEANLIFE_BASE_URL}/partners/bookings" \
-H "x-api-key: ${CLEANLIFE_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"externalReference": "ORDER-20260615-001",
"contactId": "aaaaaaaa-0000-0000-0000-000000000001",
"addressId": "bbbbbbbb-0000-0000-0000-000000000002",
"serviceId": "dddddddd-0000-0000-0000-000000000004",
"date": "2026-06-20",
"timeslot": {
"startAt": "09:00",
"endAt": "12:00"
}
}'
Get Booking Status
curl -X GET "${CLEANLIFE_BASE_URL}/partners/bookings/11111111-0000-0000-0000-000000000001/status" \
-H "x-api-key: ${CLEANLIFE_API_KEY}"
List Cancellation Reasons
curl -X GET "${CLEANLIFE_BASE_URL}/partners/bookings/cancellation-reasons" \
-H "x-api-key: ${CLEANLIFE_API_KEY}"
Cancel a Booking
curl -X PATCH "${CLEANLIFE_BASE_URL}/partners/bookings/11111111-0000-0000-0000-000000000001/cancel" \
-H "x-api-key: ${CLEANLIFE_API_KEY}" \
-H "Content-Type: application/json" \
-d '{"reasonId": "aaaaaaaa-0000-0000-0000-000000000001", "notes": "Customer requested cancellation."}'
JavaScript (fetch)
const BASE_URL = process.env.CLEANLIFE_BASE_URL;
const API_KEY = process.env.CLEANLIFE_API_KEY;
const headers = {
'x-api-key': API_KEY,
'Content-Type': 'application/json',
};
// Find or Create Contact
async function findOrCreateContact({
phone,
name,
latitude,
longitude,
cityName,
districtName,
streetName,
}) {
const response = await fetch(`${BASE_URL}/partners/contacts`, {
method: 'POST',
headers,
body: JSON.stringify({
phone,
name,
...(latitude && longitude ? { latitude, longitude } : {}),
...(cityName && { cityName }),
...(districtName && { districtName }),
...(streetName && { streetName }),
}),
});
const data = await response.json();
if (!data.success) throw new Error(data.error.code + ': ' + data.error.message);
return data.data; // { id, name, phone, addressId, isNewContact }
}
// Create Booking
async function createBooking(bookingData) {
const response = await fetch(`${BASE_URL}/partners/bookings`, {
method: 'POST',
headers,
body: JSON.stringify(bookingData),
});
const data = await response.json();
if (!data.success) throw new Error(data.error.code + ': ' + data.error.message);
return data.data;
}
// Get Booking Status
async function getBookingStatus(bookingId) {
const response = await fetch(`${BASE_URL}/partners/bookings/${bookingId}/status`, { headers });
const data = await response.json();
if (!data.success) throw new Error(data.error.code + ': ' + data.error.message);
return data.data;
}
TypeScript (axios)
import axios, { AxiosInstance } from 'axios';
interface BookingResponse {
bookingId: string;
externalReference: string | null;
status: string;
paymentStatus: string;
trackingReference: string;
date: string;
timeslot: { startAt: string; endAt: string; endsAtNextDay: boolean };
appointment: null | { id: string; name: string; status: string; scheduledStartDateTime: string; scheduledEndDateTime: string };
}
interface PartnerApiSuccess<T> { success: true; data: T; }
interface PartnerApiError { success: false; error: { code: string; message: string; requestId: string }; }
class CleanLifePartnerClient {
private client: AxiosInstance;
constructor(apiKey: string, baseURL: string) {
this.client = axios.create({
baseURL,
timeout: 30_000,
headers: {
'x-api-key': apiKey,
'Content-Type': 'application/json',
},
});
}
async findOrCreateContact(data: {
phone: string;
name: string;
latitude?: string;
longitude?: string;
cityName?: string;
districtName?: string;
streetName?: string;
}): Promise<{
id: string;
name: string;
phone: string;
addressId: string | null;
isNewContact: boolean;
}> {
const response = await this.client.post<
PartnerApiSuccess<{
id: string;
name: string;
phone: string;
addressId: string | null;
isNewContact: boolean;
}>
>('/partners/contacts', data);
return response.data.data;
}
async createBooking(data: {
externalReference?: string;
contactId: string;
addressId: string;
serviceId: string;
date: string;
timeslot: { startAt: string; endAt: string };
}): Promise<BookingResponse> {
const response = await this.client.post<PartnerApiSuccess<BookingResponse>>('/partners/bookings', data);
return response.data.data;
}
async getBookingStatus(bookingId: string): Promise<BookingResponse> {
const response = await this.client.get<PartnerApiSuccess<BookingResponse>>(
`/partners/bookings/${bookingId}/status`
);
return response.data.data;
}
async listCancellationReasons(): Promise<Array<{ id: string; nameEn: string; nameAr: string }>> {
const response = await this.client.get<PartnerApiSuccess<Array<{ id: string; nameEn: string; nameAr: string }>>>(
'/partners/bookings/cancellation-reasons',
);
return response.data.data;
}
async cancelBooking(bookingId: string, options: { reasonId: string; notes?: string }): Promise<BookingResponse> {
const response = await this.client.patch<PartnerApiSuccess<BookingResponse>>(
`/partners/bookings/${bookingId}/cancel`,
options,
);
return response.data.data;
}
async confirmPayment(bookingId: string, paymentReference?: string): Promise<BookingResponse> {
const response = await this.client.post<PartnerApiSuccess<BookingResponse>>(
`/partners/bookings/${bookingId}/confirm-payment`,
paymentReference ? { paymentReference } : {}
);
return response.data.data;
}
}
export { CleanLifePartnerClient };
Python (requests)
import os
import requests
from typing import Optional
class CleanLifePartnerClient:
def __init__(self, api_key: str, base_url: str):
self.base_url = base_url.rstrip('/')
self.session = requests.Session()
self.session.headers.update({
'x-api-key': api_key,
'Content-Type': 'application/json',
})
self.session.timeout = 30
def find_or_create_contact(
self,
phone: str,
name: str,
*,
latitude: Optional[str] = None,
longitude: Optional[str] = None,
city_name: Optional[str] = None,
district_name: Optional[str] = None,
street_name: Optional[str] = None,
) -> dict:
body = {'phone': phone, 'name': name}
if latitude and longitude:
body['latitude'] = latitude
body['longitude'] = longitude
if city_name:
body['cityName'] = city_name
if district_name:
body['districtName'] = district_name
if street_name:
body['streetName'] = street_name
response = self.session.post(f'{self.base_url}/partners/contacts', json=body)
response.raise_for_status()
result = response.json()
if not result['success']:
raise Exception(f"{result['error']['code']}: {result['error']['message']}")
return result['data']
def create_booking(self, data: dict) -> dict:
response = self.session.post(f'{self.base_url}/partners/bookings', json=data)
response.raise_for_status()
result = response.json()
if not result['success']:
raise Exception(f"{result['error']['code']}: {result['error']['message']}")
return result['data']
def get_booking_status(self, booking_id: str) -> dict:
response = self.session.get(f'{self.base_url}/partners/bookings/{booking_id}/status')
response.raise_for_status()
result = response.json()
if not result['success']:
raise Exception(result['error']['code'])
return result['data']
def list_cancellation_reasons(self) -> list:
response = self.session.get(f'{self.base_url}/partners/bookings/cancellation-reasons')
response.raise_for_status()
return response.json()['data']
def cancel_booking(self, booking_id: str, reason_id: str, notes: Optional[str] = None) -> dict:
body = {'reasonId': reason_id}
if notes:
body['notes'] = notes
response = self.session.patch(f'{self.base_url}/partners/bookings/{booking_id}/cancel', json=body)
response.raise_for_status()
return response.json()['data']
def confirm_payment(self, booking_id: str, payment_reference: Optional[str] = None) -> dict:
body = {}
if payment_reference:
body['paymentReference'] = payment_reference
response = self.session.post(
f'{self.base_url}/partners/bookings/{booking_id}/confirm-payment',
json=body
)
response.raise_for_status()
return response.json()['data']
# Usage
client = CleanLifePartnerClient(
api_key=os.environ['CLEANLIFE_API_KEY'],
base_url=os.environ['CLEANLIFE_BASE_URL']
)
booking = client.create_booking({
'externalReference': 'ORDER-20260615-001',
'contactId': 'aaaaaaaa-0000-0000-0000-000000000001',
'addressId': 'bbbbbbbb-0000-0000-0000-000000000002',
'serviceId': 'dddddddd-0000-0000-0000-000000000004',
'date': '2026-06-20',
'timeslot': {'startAt': '09:00', 'endAt': '12:00'}
})
print(booking['bookingId'])
C#
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
public class CleanLifePartnerClient
{
private readonly HttpClient _httpClient;
public CleanLifePartnerClient(string apiKey, string baseUrl)
{
_httpClient = new HttpClient { BaseAddress = new Uri(baseUrl), Timeout = TimeSpan.FromSeconds(30) };
_httpClient.DefaultRequestHeaders.Add("x-api-key", apiKey);
}
public async Task<JsonElement> CreateBookingAsync(object bookingData)
{
var response = await _httpClient.PostAsJsonAsync("/partners/bookings", bookingData);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
if (!result.GetProperty("success").GetBoolean())
{
var error = result.GetProperty("error");
throw new Exception($"{error.GetProperty("code").GetString()}: {error.GetProperty("message").GetString()}");
}
return result.GetProperty("data");
}
public async Task<JsonElement> GetBookingStatusAsync(string bookingId)
{
var response = await _httpClient.GetAsync($"/partners/bookings/{bookingId}/status");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
return result.GetProperty("data");
}
}
PHP
<?php
class CleanLifePartnerClient
{
private string $baseUrl;
private string $apiKey;
public function __construct(string $apiKey, string $baseUrl)
{
$this->apiKey = $apiKey;
$this->baseUrl = rtrim($baseUrl, '/');
}
private function request(string $method, string $path, array $body = null): array
{
$ch = curl_init($this->baseUrl . $path);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'x-api-key: ' . $this->apiKey,
'Content-Type: application/json',
]);
if ($method === 'POST' || $method === 'PATCH') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
}
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if (!$data['success']) {
throw new RuntimeException($data['error']['code'] . ': ' . $data['error']['message']);
}
return $data['data'];
}
public function findOrCreateContact(
string $phone,
string $name,
?string $latitude = null,
?string $longitude = null,
?string $cityName = null,
?string $districtName = null,
?string $streetName = null,
): array {
$body = ['phone' => $phone, 'name' => $name];
if ($latitude !== null && $longitude !== null) {
$body['latitude'] = $latitude;
$body['longitude'] = $longitude;
}
if ($cityName !== null) {
$body['cityName'] = $cityName;
}
if ($districtName !== null) {
$body['districtName'] = $districtName;
}
if ($streetName !== null) {
$body['streetName'] = $streetName;
}
return $this->request('POST', '/partners/contacts', $body);
}
public function createBooking(array $bookingData): array
{
return $this->request('POST', '/partners/bookings', $bookingData);
}
public function getBookingStatus(string $bookingId): array
{
return $this->request('GET', "/partners/bookings/{$bookingId}/status");
}
public function listCancellationReasons(): array
{
return $this->request('GET', '/partners/bookings/cancellation-reasons');
}
public function cancelBooking(string $bookingId, string $reasonId, ?string $notes = null): array
{
$body = ['reasonId' => $reasonId];
if ($notes !== null) {
$body['notes'] = $notes;
}
return $this->request('PATCH', "/partners/bookings/{$bookingId}/cancel", $body);
}
public function confirmPayment(string $bookingId, string $paymentReference = null): array
{
$body = $paymentReference ? ['paymentReference' => $paymentReference] : [];
return $this->request('POST', "/partners/bookings/{$bookingId}/confirm-payment", $body);
}
}
// Usage
$client = new CleanLifePartnerClient(
getenv('CLEANLIFE_API_KEY'),
getenv('CLEANLIFE_BASE_URL')
);
$booking = $client->createBooking([
'externalReference' => 'ORDER-20260615-001',
'contactId' => 'aaaaaaaa-0000-0000-0000-000000000001',
'addressId' => 'bbbbbbbb-0000-0000-0000-000000000002',
'serviceId' => 'dddddddd-0000-0000-0000-000000000004',
'date' => '2026-06-20',
'timeslot' => ['startAt' => '09:00', 'endAt' => '12:00'],
]);
echo $booking['bookingId'];