Overview
The Partner API uses API Key authentication. Every request must carry a valid API Key issued by CleanLife. There is no OAuth flow, no JWT, and no session management required.
The API Key acts as both your identity credential and your permission token. It is validated on every request.
How Authentication Works
- Include your API Key in the
x-api-key request header on every request.
- The platform verifies that the key is valid, active, and associated with your partner account.
- If an IP allowlist is configured for your key, requests from other addresses are rejected.
- Your request rate is checked against your configured limit (if any).
- If a domain allowlist is configured on your account, browser-originated requests are validated against your registered domains.
- The platform checks that your key has the permission required for the endpoint you are calling.
- If all checks pass, your request proceeds.
| Header | Description |
|---|
x-api-key | Your partner API key. Required on every request. |
The key can also be passed as a query parameter api_key, but this is strongly discouraged because query parameters appear in server logs, proxy logs, and browser history.
API Keys are opaque strings generated by the platform. The exact format is internal and may vary. Do not parse or derive meaning from the key value.
Authentication Examples
cURL
curl -X GET "https://apiv3.thecleanlife.dev/v1/partners/services" \
-H "x-api-key: YOUR_API_KEY_HERE"
JavaScript (Node.js / fetch)
const response = await fetch('https://apiv3.thecleanlife.dev/v1/partners/services', {
method: 'GET',
headers: {
'x-api-key': process.env.CLEANLIFE_API_KEY,
'Content-Type': 'application/json',
},
});
const data = await response.json();
Python (requests)
import requests
response = requests.get(
'https://apiv3.thecleanlife.dev/v1/partners/services',
headers={
'x-api-key': 'YOUR_API_KEY_HERE',
'Content-Type': 'application/json',
}
)
data = response.json()
TypeScript (axios)
import axios from 'axios';
const client = axios.create({
baseURL: 'https://apiv3.thecleanlife.dev/v1',
headers: {
'x-api-key': process.env.CLEANLIFE_API_KEY!,
'Content-Type': 'application/json',
},
});
const { data } = await client.get('/partners/services');
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", "YOUR_API_KEY_HERE");
var response = await client.GetAsync("https://apiv3.thecleanlife.dev/v1/partners/services");
var json = await response.Content.ReadAsStringAsync();
PHP
<?php
$ch = curl_init("https://apiv3.thecleanlife.dev/v1/partners/services");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'x-api-key: YOUR_API_KEY_HERE',
'Content-Type: application/json',
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
Permissions Model
Each API Key is granted a set of permissions. If you attempt to call an endpoint without the required permission, you will receive a 403 Forbidden response.
| Permission | Endpoints |
|---|
partner_bookings_create | POST /partners/bookings |
partner_bookings_read | GET /partners/bookings, GET /partners/bookings/:id/status |
partner_bookings_update | PATCH /partners/bookings/:id |
partner_bookings_cancel | PATCH /partners/bookings/:id/cancel |
partner_bookings_confirm_payment | POST /partners/bookings/:id/confirm-payment |
partner_services_read | GET /partners/services, GET /partners/services/:id |
partner_pricing_read | GET /partners/pricing/tiers, POST /partners/pricing/calculate-price |
partner_timeslots_read | GET /partners/timeslots/available |
partner_webhooks_manage | All /partners/webhooks/* endpoints |
partner_contacts_read | POST /partners/contacts, GET /partners/contacts/:contactId |
Request only the permissions you actually need. Following the principle of least privilege reduces risk if your key is ever compromised.
Domain Allowlist (Optional)
If your partner account has allowed webhook domains configured, the platform will additionally validate that incoming requests originate from one of your registered domains.
Domain detection happens in the following priority order:
Origin request header
Referer request header
x-partner-domain request header (optional fallback)
Behavior:
| Scenario | Result |
|---|
| Partner has no domains configured | No domain check — all requests pass |
| Partner has domains configured, no domain detected in request | Passes (server-to-server calls) |
| Partner has domains configured, domain detected, matches | Passes |
| Partner has domains configured, domain detected, no match | 403 INVALID_PARTNER_DOMAIN |
| Matched domain is a sandbox domain | 403 SANDBOX_DOMAIN_NOT_ALLOWED (currently disabled) |
This means server-to-server integrations (where there is no Origin or Referer header) are never blocked by the domain check, even when domains are configured.
Authentication Errors
| HTTP Status | Error Code | Cause |
|---|
401 Unauthorized | UNAUTHORIZED | API key is missing, invalid, or belongs to a suspended partner |
403 Forbidden | FORBIDDEN | API key is valid but does not have the required permission |
403 Forbidden | INVALID_PARTNER_DOMAIN | Request domain is not in your partner’s allowlist |
403 Forbidden | SANDBOX_DOMAIN_NOT_ALLOWED | Request came from a sandbox domain (currently disabled globally) |
429 Too Many Requests | RATE_LIMIT_EXCEEDED | Requests exceeded your rate limit within the current window |
Security Recommendations
- Never expose your API Key client-side. Store it as a server-side environment variable (e.g.,
CLEANLIFE_API_KEY).
- Rotate your key periodically. Contact the CleanLife team to issue a new key.
- Use HTTPS exclusively. Never call the API over plain HTTP.
- Log and monitor failed authentication responses (
401, 403) to detect unauthorized usage.
- IP allowlist — ask the CleanLife team to restrict your API Key to specific egress IP addresses of your servers.
- Scope permissions carefully — request only the permissions your integration needs.