Skip to main content

Overview

Creates a new booking on the CleanLife platform on behalf of one of your customers. This is the core endpoint of the Partner API. The booking creation flow varies based on your partner account’s paymentResponsibility setting:
  • CLEANOS responsibility: CleanLife handles payment. Depending on the service price and any coupons applied, the booking may transition to waiting payment (CleanLife sends a payment link asynchronously) or directly to in progress (free service or coupon covers the cost).
  • PARTNER responsibility: Your system handles payment. The booking is set to in progress immediately, bypassing CleanLife payment processing. You are expected to confirm payment later via POST /partners/bookings/:bookingId/confirm-payment.

Endpoint

POST /partners/bookings

Authentication

Requires a valid API Key with the partner_bookings_create permission.

Request Headers

HeaderRequiredValue
x-api-keyYesYour partner API key
Content-TypeYesapplication/json

Request Body

{
  "externalReference": "ORDER-12345",
  "contactId": "00000000-0000-0000-0000-000000000001",
  "addressId": "00000000-0000-0000-0000-000000000002",
  "serviceId": "00000000-0000-0000-0000-000000000004",
  "packageId": null,
  "customServices": [
    { "id": "00000000-0000-0000-0000-000000000005", "quantity": 2 }
  ],
  "hours": 3,
  "date": "2026-06-20",
  "timeslot": {
    "startAt": "09:00",
    "endAt": "12:00"
  },
  "numberOfTeams": 1,
  "workType": "REGULAR",
  "promoCode": null,
  "contractCode": null,
  "discountCode": null,
  "couponIds": [],
  "codes": [],
  "userId": null
}

Field Reference

FieldTypeRequiredDescription
contactIdUUIDYesID of the customer contact. Obtain via POST /partners/contacts.
addressIdstring (UUID)YesID of the customer’s service address. Used to determine the service zone.
serviceIdUUIDYesService ID from GET /partners/services. Must be enabled for your partner account. The service category is resolved automatically — do not send categoryId.
timeslotobjectYesAppointment timeslot.
timeslot.startAtstringYesStart time in HH:MM or HH:MM:SS format (Riyadh local time).
timeslot.endAtstringYesEnd time in HH:MM or HH:MM:SS format (Riyadh local time).
dateISO date stringYesService date in YYYY-MM-DD format (Riyadh local date).
externalReferencestringNoYour own reference ID for this booking (e.g., your internal order number). Must be unique per partner. Max 150 characters.
packageIdUUIDNoPackage ID (for bundled service offerings).
customServicesarrayNoArray of add-on custom services with optional quantities.
customServices[].idUUIDYes (if array present)ID of the custom service.
customServices[].quantityinteger ≥ 1NoQuantity, defaults to 1.
hoursinteger ≥ 1NoDuration in hours. Defaults to 1.
numberOfTeamsinteger ≥ 2NoNumber of cleaning teams. Defaults to 1 (implied). Only set if more than one team is needed; minimum is 2 when specified.
workTypestring (enum)NoType of work. See WorkType enum.
userIdUUIDNoCleanLife platform user ID (for coupon validation purposes).
couponIdsUUID[]NoUp to 2 coupon IDs to apply.
codesstring[]NoUp to 2 coupon/promo codes to apply.
promoCodestringNoPromotional code (legacy field; prefer codes).
contractCodestringNoContract code for enterprise/B2B pricing.
discountCodestringNoDiscount code.

Example Request

curl -X POST "https://apiv3.thecleanlife.dev/v1/partners/bookings" \
  -H "x-api-key: YOUR_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",
    "hours": 3,
    "date": "2026-06-20",
    "timeslot": {
      "startAt": "09:00",
      "endAt": "12:00"
    }
  }'

Success Response

HTTP Status: 200 OK
{
  "success": true,
  "data": {
    "bookingId": "11111111-0000-0000-0000-000000000001",
    "externalReference": "ORDER-20260615-001",
    "appointmentId": "22222222-0000-0000-0000-000000000002",
    "status": "in progress",
    "paymentStatus": "NOT_REQUIRED",
    "trackingReference": "SA-0042",
    "date": "2026-06-20T00:00:00.000Z",
    "timeslot": {
      "startAt": "09:00",
      "endAt": "12:00",
      "endsAtNextDay": false
    },
    "appointment": {
      "id": "22222222-0000-0000-0000-000000000002",
      "name": "SA-0042",
      "status": "Scheduled",
      "scheduledStartDateTime": "2026-06-20T09:00:00+03:00",
      "scheduledEndDateTime": "2026-06-20T12:00:00+03:00"
    }
  }
}

Response Fields

FieldTypeDescription
bookingIdUUIDCleanLife booking ID. Use this for all subsequent booking operations.
externalReferencestring / nullThe externalReference you provided, or null if not provided.
appointmentIdUUID / nullThe service appointment ID, or null if not yet scheduled.
statusstringBooking status. See Booking Statuses.
paymentStatusstringPayment status. See Payment Statuses.
trackingReferencestringA human-readable reference (usually the service appointment name). Use this when communicating with your customers.
datedateThe service date.
timeslot.startAtstringStart time of the appointment.
timeslot.endAtstringEnd time of the appointment.
timeslot.endsAtNextDaybooleantrue if the appointment spans midnight.
appointmentobject / nullAppointment details if assigned, otherwise null.
appointment.statusstringSee Appointment Statuses.

Booking Statuses

StatusDescription
in progressBooking is active and the service has been scheduled.
waiting paymentBooking is pending payment from the customer (CLEANOS responsibility only).
successService was completed successfully.
canceledBooking was cancelled.
failedBooking failed (payment or validation error).

Payment Statuses

StatusDescription
NOT_REQUIREDNo payment needed (free service or no-payment-model).
PENDINGPayment has not yet been confirmed. For PARTNER responsibility: awaiting your confirm-payment call. For CLEANOS responsibility: awaiting customer payment.
PAIDPayment has been confirmed (either CleanLife payment record exists, or partner called confirm-payment).
FAILEDPayment process failed.
CANCELLEDBooking was cancelled.

Appointment Statuses

StatusDescription
NewAppointment created, not yet scheduled.
ScheduledAppointment has been assigned to a team.
ConfirmedAppointment confirmed.
In The WayTeam is traveling to the location.
In The locationTeam has arrived.
In ProgressService is underway.
FinishedService work is done.
CompletedAppointment fully completed.
Cannot CompleteTeam could not complete the service.
CancelledAppointment was cancelled.

Error Responses

HTTP StatusCodeDescription
400VALIDATION_ERROROne or more required fields are missing or invalid (e.g. missing serviceId)
401UNAUTHORIZEDInvalid or missing API key
403FORBIDDENMissing partner_bookings_create permission
404RESOURCE_NOT_FOUND / SERVICE_NOT_ALLOWEDserviceId not found or not enabled for your partner account
409DUPLICATE_EXTERNAL_REFERENCEThe externalReference is already used by another booking under your account
422BOOKING_CREATION_FAILEDThe booking could not be completed — typically a payment or validation error

Business Rules

  1. externalReference uniqueness: If you provide an externalReference, it must be unique across all bookings for your partner account. Duplicate values result in a 409 error. This is your primary tool for preventing duplicate bookings — generate a unique ID on your side before calling the API.
  2. serviceId is required. Choose a serviceId from GET /partners/services. Do not send categoryId — the platform resolves the service category automatically.
  3. Timeslot interpretation. All timeslot times (startAt, endAt) are interpreted as Asia/Riyadh local time (UTC+3).
  4. Payment flow — CLEANOS responsibility:
    • If the booking total is zero (free service or full coupon), it goes directly to in progress.
    • If the booking requires payment, CleanLife sends a payment link asynchronously. The booking enters waiting payment. A booking.payment_failed webhook is sent if the async payment link fails.
  5. Payment flow — PARTNER responsibility:
    • The booking is immediately set to in progress.
    • You must call POST /partners/bookings/:bookingId/confirm-payment once you collect payment from your customer.
  6. Failed reference save. In the rare event that a booking is created but the partner reference cannot be saved, the booking is marked as FAILED so you can safely retry with the same externalReference.
  7. Webhook on creation. A booking.created webhook event is fired immediately after successful booking creation.

Notes

  • The appointmentId may be null immediately after creation in some scheduling flows. Monitor the booking.appointment_status_changed webhook event.
  • The trackingReference is the value you should display to your customers for tracking purposes.
  • Do not rely on the appointment object being present at creation time.

Integration Tips

  • Always use externalReference to tie CleanLife bookings back to your internal orders. Without it, deduplication on retry is much harder.
  • Obtain contactId first via POST /partners/contacts before creating a booking.
  • Verify available timeslots first using GET /partners/timeslots/available before creating a booking.
  • Calculate the expected price first using POST /partners/pricing/calculate-price to avoid surprises.

Common Mistakes

MistakeConsequenceFix
Not providing externalReferenceCannot deduplicate retriesAlways generate and pass a unique reference
Using HH:MM:SS vs HH:MM inconsistentlyBoth formats are acceptedUse HH:MM for consistency
Passing a date in UTC instead of Riyadh localBooking created on wrong dateAlways use YYYY-MM-DD in Riyadh local time
Reusing externalReference after a failed booking409 DUPLICATE_EXTERNAL_REFERENCE if the failed booking reference was savedThe system compensates failed orphaned bookings; if you receive 422 BOOKING_CREATION_FAILED, the reference is not saved and you can retry

  • POST /partners/contacts — Get or create contactId
  • GET /partners/timeslots/available — Get available timeslots before booking
  • POST /partners/pricing/calculate-price — Preview the booking price
  • GET /partners/bookings/:bookingId/status — Poll booking status
  • PATCH /partners/bookings/:bookingId — Update the booking date/timeslot
  • PATCH /partners/bookings/:bookingId/cancel — Cancel the booking
  • POST /partners/bookings/:bookingId/confirm-payment — Confirm payment (PARTNER responsibility only)