code field. Build your error handling logic around the code, not the message.
Error Envelope
Complete Error Reference
Authentication & Authorization Errors
| HTTP Status | Code | Cause | How to Fix |
|---|---|---|---|
401 | UNAUTHORIZED | API key is missing, invalid, expired, or the associated partner account is suspended | Verify your x-api-key header is set correctly; contact support if the key is valid but still rejected |
403 | FORBIDDEN | Valid API key but missing the required permission for this endpoint | Request the missing permission from the CleanLife team |
403 | INVALID_PARTNER_DOMAIN | The Origin or Referer header on your request does not match any registered domain for this partner | Register your domain with CleanLife or remove the Origin header for server-to-server calls |
403 | SANDBOX_DOMAIN_NOT_ALLOWED | Your request came from a domain registered as a sandbox domain; sandbox access is currently disabled | Contact CleanLife to register a production domain |
Validation Errors
| HTTP Status | Code | Cause | How to Fix |
|---|---|---|---|
400 | VALIDATION_ERROR | One or more request fields failed validation (wrong type, missing required field, out-of-range value, invalid UUID, etc.) | Read the details field for a breakdown of which fields failed and why |
| Field | Validation Rule | Error |
|---|---|---|
serviceId | Must be a valid UUID v4; required | serviceId must be a UUID |
contactId | Must be a valid UUID v4 | contactId must be a UUID |
phone | Must be a valid Saudi phone number | phone must be a valid phone number |
date | Must be an ISO 8601 date string | date must be a valid ISO 8601 date string |
timeslot.startAt | Must be a string | timeslot.startAt must be a string |
hours | Must be integer ≥ 1 | hours must not be less than 1 |
limit (pagination) | Must be integer 1–100 | limit must not be greater than 100 |
couponIds | Max 2 items | couponIds must contain no more than 2 elements |
Resource Errors
| HTTP Status | Code | Cause | How to Fix |
|---|---|---|---|
404 | RESOURCE_NOT_FOUND | Booking, contact, service, webhook subscription, or delivery not found — either does not exist or belongs to a different partner | Verify the ID and that the resource was created by your partner account |
404 | SERVICE_NOT_ALLOWED | The serviceId is not enabled for your partner account | Use a serviceId from GET /partners/services |
Booking Business Rule Errors
| HTTP Status | Code | Cause | How to Fix |
|---|---|---|---|
409 | DUPLICATE_EXTERNAL_REFERENCE | You attempted to create or update a booking with an externalReference that already exists for your partner account | Use a unique externalReference value per booking |
422 | BOOKING_CREATION_FAILED | The booking could not be completed — typically a payment or validation failure | Review your booking request fields; retry once; if persistent, contact support |
422 | BOOKING_NOT_EDITABLE | Attempted to update a booking that is not in in progress or waiting payment status | Only bookings in these two statuses can be rescheduled or have their externalReference updated |
422 | BOOKING_APPOINTMENT_NOT_FOUND | Attempted to reschedule a booking that has no associated service appointment | This is an unexpected state; contact support |
422 | BOOKING_ALREADY_CANCELLED | Attempted to cancel a booking that is already cancelled | No action needed; the booking is already cancelled |
422 | BOOKING_NOT_PAYABLE | Attempted to confirm payment for a cancelled or failed booking | Payment can only be confirmed for active bookings |
422 | PAYMENT_RESPONSIBILITY_MISMATCH | Attempted to call confirm-payment but your partner account has paymentResponsibility = CLEANOS | Only PARTNER responsibility accounts can confirm payments |
422 | PAYMENT_ALREADY_CONFIRMED | Attempted to confirm payment for a booking whose payment was already confirmed | Payment confirmation is idempotent in intent but rejected as a duplicate; no action needed |
Webhook Errors
| HTTP Status | Code | Cause | How to Fix |
|---|---|---|---|
400 | VALIDATION_ERROR (message: “Webhook URL must use HTTPS”) | Webhook subscription URL uses HTTP when HTTPS is enforced | Use an HTTPS URL |
400 | VALIDATION_ERROR (message: “Webhook URL host is not allowed”) | Webhook URL points to a blocked host (localhost, 127.x.x.x, 10.x.x.x, 192.168.x.x, etc.) | Use a publicly routable HTTPS URL |
400 | VALIDATION_ERROR (message: “Partner has no allowed webhook domains configured”) | Your partner account has no allowed domains registered, so no webhook URL can be validated | Contact CleanLife to register your webhook domain |
400 | VALIDATION_ERROR (message: “Webhook domain is not allowed for this partner”) | The hostname of the webhook URL is not in your allowed domains list | Register the domain with CleanLife or use a URL from an already-allowed domain |
404 | RESOURCE_NOT_FOUND | Webhook subscription not found | Verify the subscription ID |
404 | DELIVERY_NOT_RETRYABLE | Attempted to retry a delivery that is not in FAILED status | Only FAILED deliveries can be manually retried |
Rate Limit Errors
| HTTP Status | Code | Cause | How to Fix |
|---|---|---|---|
429 | RATE_LIMIT_EXCEEDED | Exceeded your API key’s request limit within the current 60-second sliding window | Wait for the window to reset and retry; implement exponential backoff |
Server Errors
| HTTP Status | Code | Cause | How to Fix |
|---|---|---|---|
500 | INTERNAL_ERROR | Unexpected server-side error | Retry with exponential backoff; if persistent, contact support with the requestId from the response |
Using requestId for Support
Every response (including errors) includes a requestId in the error envelope and an X-Request-Id response header. If you contact CleanLife support about an unexpected error, always include this value — it allows the engineering team to trace the exact request through server logs.
Retry Strategy
| Error Code | Retryable? | Notes |
|---|---|---|
VALIDATION_ERROR | No | Fix the request payload before retrying |
UNAUTHORIZED | No | Fix the API key before retrying |
FORBIDDEN | No | Request the missing permission |
RESOURCE_NOT_FOUND | No | The resource does not exist |
DUPLICATE_EXTERNAL_REFERENCE | No | Use a different externalReference |
BOOKING_CREATION_FAILED | Yes | Retry once with the same or corrected payload |
RATE_LIMIT_EXCEEDED | Yes | Back off and retry after the 60-second window |
INTERNAL_ERROR | Yes | Retry with exponential backoff (see Best Practices) |