Démarrage rapide en 5 minutes
- Créez une clé : un administrateur ouvre Paramètres → Développeurs & API, clique « Nouvelle clé » et copie la valeur
cp_live_…(affichée une seule fois). - Stockez-la côté serveur dans une variable d'environnement
CLEANPLUS_KEY. - Testez l'appel ci-dessous : vous devez recevoir la liste de vos prestations.
- Déclarez un webhook https dans le même écran et cliquez « Tester » pour valider la réception.
curl https://cleanplus.app/api/public/v1/services -H "Authorization: Bearer $CLEANPLUS_KEY"Parcours type d'un tunnel de réservation :
1. GET /availability → afficher les créneaux libres pour la durée estimée
2. POST /bookings → à la validation : client + site + intervention créés
3. Webhooks → booking.rescheduled / cancelled / intervention.completed
4. PATCH ou /cancel → modification ou annulation demandée par le clientAuthentification & droits
Chaque requête porte la clé dans l'en-tête Authorization. Les clés sont propres à une organisation : toutes les données lues ou créées appartiennent à cette organisation. Elles sont stockées chiffrées (empreinte SHA-256) et révocables à tout moment. N'exposez jamais une clé dans le navigateur ou une application mobile : appelez l'API depuis votre serveur.
Authorization: Bearer cp_live_xxxxxxxxxxxxxxxxxxxxxxxx| Droit | Autorise |
|---|---|
| availability:read | Lire les créneaux libres et le catalogue des prestations |
| bookings:write | Créer, modifier, reprogrammer et annuler des réservations |
| bookings:read | Consulter une réservation |
Bonnes pratiques : une clé par intégration, droits minimum nécessaires, rotation en cas de départ d'un prestataire.
Conventions
- URL de base :
https://cleanplus.app/api/public/v1— HTTPS uniquement. - Format : JSON UTF-8, en-tête
Content-Type: application/jsonpour POST et PATCH. - Fuseau horaire : dates
AAAA-MM-JJet heuresHH:MMexprimées à l'heure de Paris (Europe/Paris) ; horodatages techniques en ISO 8601 UTC. - Idempotence :
POST /bookingsexigeIdempotency-Key(ex. l'id de votre commande). Rejouer la même clé renvoie la même réservation, sans doublon — idéal après un timeout réseau. - Limite de débit : 120 requêtes par minute et par clé ; au-delà, réponse 429.
- CORS : autorisé, mais l'usage serveur est recommandé pour protéger la clé.
- Compatibilité : des champs peuvent être ajoutés aux réponses sans changer de version ; ignorez les champs inconnus.
Endpoints
/availabilityGET/servicesPOST/bookingsGET/bookings/{id}PATCH/bookings/{id}POST/bookings/{id}/cancelCréneaux libres
/availabilityscope : availability:readRenvoie les créneaux où au moins agents_count agents actifs sont libres pendant toute la durée demandée, dans les heures d'ouverture (8h–19h), en tenant compte des interventions déjà planifiées, des absences et d'un temps de trajet de 30 minutes entre deux interventions. Les créneaux passés sont exclus.
Paramètres de requête
| Champ | Type | Description |
|---|---|---|
| date_fromrequis | date | Premier jour recherché (AAAA-MM-JJ). |
| date_torequis | date | Dernier jour inclus. Plage de 31 jours maximum. |
| duration_minutesrequis | integer | Durée estimée de la prestation, de 15 à 720 minutes. |
| agents_count | integer | Nombre d'agents simultanés, de 1 à 10 (défaut 1). |
| lat / lng | number | Position du lieu d'intervention (enregistrée, non utilisée pour le tri en v1). |
curl "https://cleanplus.app/api/public/v1/availability?date_from=2026-10-12&date_to=2026-10-16&duration_minutes=120&agents_count=1" \
-H "Authorization: Bearer $CLEANPLUS_KEY"{
"timezone": "Europe/Paris",
"slots": [
{ "date": "2026-10-12", "start_time": "08:00", "end_time": "10:00", "available_agents_count": 3 },
{ "date": "2026-10-12", "start_time": "08:30", "end_time": "10:30", "available_agents_count": 3 }
]
}Catalogue des prestations
/servicesscope : availability:readListe les prestations actives de l'organisation avec leur prix unitaire HT, pour alimenter votre simulateur de tarif.
{
"services": [
{ "id": "…", "name": "Ménage complet", "description": "Toutes pièces", "category": "Particuliers",
"unit_price_ht": 32.5, "unit": "heure" }
]
}Créer une réservation
/bookingsscope : bookings:writeEn un seul appel : le client est retrouvé (par e-mail puis SIRET) ou créé — ses informations existantes ne sont jamais modifiées —, le site est retrouvé par adresse ou créé, puis l'intervention est planifiée et les agents disponibles sont affectés automatiquement. Elle apparaît dans le planning avec le badge « Réservation en ligne ». Le créneau est revérifié au moment de la création.
En-têtes
| Champ | Type | Description |
|---|---|---|
| Idempotency-Keyrequis | string | Identifiant unique de la commande (≤ 200 caractères). |
Corps JSON
| Champ | Type | Description |
|---|---|---|
| external_ref | string ≤120 | Votre identifiant de commande, renvoyé dans chaque réponse et webhook. |
| customer.type | enum | particulier (défaut) ou professionnel. |
| customer.namerequis | string ≤200 | Nom de famille ou raison sociale. |
| customer.first_name | string ≤100 | Prénom. |
| customer.company_name | string ≤200 | Société (clients professionnels). |
| customer.emailrequis | email ≤255 | Sert à retrouver un client existant (jamais écrasé). |
| customer.phone | string ≤30 | Téléphone. |
| customer.siret | 14 chiffres | Rapprochement des clients professionnels. |
| site.addressrequis | string 3–300 | Adresse d'intervention. Un site existant à la même adresse est réutilisé. |
| site.name | string ≤200 | Nom du site (défaut : client + adresse). |
| site.postal_code / city | string | Code postal (≤10) et ville (≤100). |
| site.latitude / longitude | number | Coordonnées GPS. |
| site.access_instructions | string ≤2000 | Consignes d'accès visibles par les agents. |
| site.access_codes | string ≤500 | Digicode, boîte à clés… |
| site.surface | string ≤50 | Surface, ex. « 85 m² ». |
| site.premises_type | string ≤100 | Appartement, bureaux, local commercial… |
| slot.daterequis | date | Jour choisi (AAAA-MM-JJ, heure de Paris). |
| slot.start_timerequis | HH:MM | Heure de début, issue de /availability. |
| slot.duration_minutesrequis | integer | 15 à 720 minutes. Doit correspondre à la recherche de créneaux. |
| agents_count | integer | 1 à 10 (défaut 1). |
| service | string ≤200 | Prestation commandée (nom libre ou issu de /services). |
| notes | string ≤5000 | Options, précisions, résultat du simulateur. |
| attachments[] | array ≤20 | Photos/vidéos : { url (https, ≤2000), name? }. Ajoutées aux documents du site. |
curl -X POST https://cleanplus.app/api/public/v1/bookings \
-H "Authorization: Bearer $CLEANPLUS_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: CMD-1042" \
-d '{
"external_ref": "CMD-1042",
"customer": { "type": "particulier", "name": "Martin", "first_name": "Julie", "email": "julie@exemple.fr", "phone": "0601020304" },
"site": { "address": "12 rue de la Paix", "postal_code": "75002", "city": "Paris",
"access_instructions": "Digicode au portail, 3e étage", "surface": "85 m²" },
"slot": { "date": "2026-10-12", "start_time": "08:00", "duration_minutes": 120 },
"service": "Ménage complet",
"notes": "Options : vitres, four",
"attachments": [{ "url": "https://votre-stockage.fr/photo-salon.jpg", "name": "Salon" }]
}'{
"id": "5d0c6e0a-2c41-4a3e-9f1b-7f3b2d1e9a10",
"external_ref": "CMD-1042",
"source": "api",
"status": "planifie",
"date": "2026-10-12",
"start_time": "08:00",
"end_time": "10:00",
"service": "Ménage complet",
"notes": "Options : vitres, four",
"site": { "id": "…", "name": "Martin — 12 rue de la Paix", "address": "12 rue de la Paix, 75002 Paris",
"access_instructions": "Digicode au portail, 3e étage" },
"customer": { "id": "…", "name": "Martin", "first_name": "Julie",
"email": "julie@exemple.fr", "phone": "0601020304" },
"agents": [ { "id": "…", "role": "principal", "name": "Sofia Benali" } ],
"created_at": "2026-10-01T14:02:11.000Z",
"updated_at": "2026-10-01T14:02:11.000Z"
}Réponses possibles : 201 créée (aussi renvoyé à l'identique en cas de rejeu idempotent), 400, 401, 403, 409 slot_unavailable, 429.
Lire une réservation
/bookings/{id}scope : bookings:readRenvoie l'objet Booking à jour (statut, horaires, agents affectés).
curl https://cleanplus.app/api/public/v1/bookings/5d0c6e0a-2c41-4a3e-9f1b-7f3b2d1e9a10 -H "Authorization: Bearer $CLEANPLUS_KEY"Modifier ou reprogrammer
/bookings/{id}scope : bookings:writeTous les champs sont facultatifs ; seuls ceux envoyés sont modifiés. Impossible sur une réservation réalisée ou annulée (409 booking_locked).
Corps JSON
| Champ | Type | Description |
|---|---|---|
| slot | object | { date, start_time, duration_minutes } — reprogramme et réattribue les agents (409 si indisponible). |
| service | string ≤200 | Nouvelle prestation. |
| notes | string ≤5000 | Remplace les notes. |
| access_instructions | string ≤2000 | Met à jour les consignes du site. |
| access_codes | string ≤500 | Met à jour les codes d'accès. |
curl -X PATCH https://cleanplus.app/api/public/v1/bookings/{id} \
-H "Authorization: Bearer $CLEANPLUS_KEY" -H "Content-Type: application/json" \
-d '{ "slot": { "date": "2026-10-13", "start_time": "14:00", "duration_minutes": 120 } }'Annuler une réservation
/bookings/{id}/cancelscope : bookings:writePasse l'intervention au statut annule et libère les agents. Le motif est conservé dans les notes.
Corps JSON
| Champ | Type | Description |
|---|---|---|
| reason | string ≤1000 | Motif d'annulation. |
curl -X POST https://cleanplus.app/api/public/v1/bookings/{id}/cancel \
-H "Authorization: Bearer $CLEANPLUS_KEY" -H "Content-Type: application/json" \
-d '{ "reason": "Annulé par le client" }'Objets
Booking
| Champ | Type | Description |
|---|---|---|
| idrequis | uuid | Identifiant CleanPlus de la réservation. |
| external_refrequis | string | null | Votre référence. |
| sourcerequis | api | app | Origine de la réservation. |
| statusrequis | enum | planifie, en_cours, termine ou annule. |
| daterequis | date | Jour d'intervention. |
| start_time / end_timerequis | HH:MM | Horaires, heure de Paris. |
| service / notesrequis | string | null | Prestation et notes. |
| siterequis | object | { id, name, address, access_instructions }. |
| customerrequis | object | { id, name, first_name, email, phone }. |
| agents[]requis | array | { id, role, name } des agents affectés. |
| created_at / updated_atrequis | datetime | Horodatages ISO 8601 (UTC). |
Slot
| Champ | Type | Description |
|---|---|---|
| daterequis | date | Jour. |
| start_time / end_timerequis | HH:MM | Début et fin, heure de Paris. |
| available_agents_countrequis | integer | Agents libres sur toute la durée. |
Service
| Champ | Type | Description |
|---|---|---|
| idrequis | uuid | Identifiant. |
| namerequis | string | Nom. |
| description / categoryrequis | string | null | Détails. |
| unit_price_htrequis | number | null | Prix unitaire HT en euros. |
| unitrequis | string | null | heure, mission, forfait… |
Error
{ "error": { "code": "validation_error", "message": "Données invalides",
"details": { "slot.start_time": ["Format HH:MM attendu"] } } }Webhooks
Déclarez une ou plusieurs adresses https dans Paramètres → Développeurs & API et choisissez les événements. Ils sont émis pour les réservations en ligne, que le changement vienne de l'API ou du planning CleanPlus.
| Événement | Déclencheur |
|---|---|
| booking.created | Réservation créée (via l'API) |
| booking.updated | Consignes, prestation ou notes modifiées |
| booking.rescheduled | Date ou horaire déplacé (API ou planning CleanPlus) |
| booking.cancelled | Réservation annulée (API ou planning) |
| intervention.completed | Intervention marquée réalisée par l'équipe |
Requête envoyée
POST https://votre-site.fr/webhooks/cleanplus
Content-Type: application/json
User-Agent: CleanPlus-Webhooks/1.0
X-CleanPlus-Event: booking.rescheduled
X-CleanPlus-Delivery: 8f1c… ← identifiant unique, à utiliser pour dédoublonner
X-CleanPlus-Signature: t=1791712320,v1=5f2b…
{ "id": "8f1c…", "type": "booking.rescheduled", "created_at": "2026-10-10T09:12:00Z",
"data": { …objet Booking… } }Vérifier la signature
HMAC-SHA256 de `${t}.${corps brut}` avec le secret du webhook. Refusez les messages de plus de 5 minutes.
import crypto from "node:crypto";
export function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return expected.length === v1.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}Relances et fiabilité
- Répondez par un code 2xx en moins de 10 secondes ; traitez ensuite de façon asynchrone.
- En cas d'échec : 5 nouvelles tentatives (1 min, 5 min, 30 min, 2 h, 6 h).
- Après 20 échecs consécutifs, l'adresse est désactivée et un administrateur est prévenu.
- Un même événement peut arriver deux fois : dédoublonnez avec
X-CleanPlus-Delivery. - L'ordre n'est pas garanti : comparez
data.updated_atavant d'écraser vos données.
Codes d'erreur
| HTTP | Code | Cause | Action |
|---|---|---|---|
| 400 | validation_error | Un champ est absent ou invalide (détail dans error.details) | Corrigez la requête, ne pas rejouer telle quelle |
| 400 | invalid_json | Le corps n'est pas un JSON valide | Vérifiez Content-Type et sérialisation |
| 400 | idempotency_key_required | En-tête Idempotency-Key absent sur POST /bookings | Ajoutez un identifiant unique de commande |
| 401 | invalid_api_key | Clé absente, erronée ou révoquée | Vérifiez la clé dans Paramètres → Développeurs & API |
| 403 | insufficient_scope | La clé n'a pas le droit requis | Générez une clé avec le bon droit |
| 404 | not_found | Réservation inexistante ou d'une autre organisation | Vérifiez l'identifiant |
| 409 | slot_unavailable | Le créneau a été pris entre-temps | Rechargez /availability et proposez un autre créneau |
| 409 | booking_locked | Réservation déjà réalisée ou annulée | Aucune modification possible |
| 429 | rate_limited | Plus de 120 requêtes par minute | Attendez puis réessayez avec un délai croissant |
| 500 | internal_error | Erreur inattendue côté CleanPlus | Réessayez ; contactez le support si cela persiste |
FAQ intégrateurs
Comment tester sans polluer le planning ?
Créez une réservation à une date lointaine avec un external_ref du type TEST-…, vérifiez les webhooks, puis annulez-la via /cancel. Vous pouvez aussi révoquer la clé de test ensuite.
Que faire en cas de 409 slot_unavailable ?
Le créneau a été pris entre l'affichage et la validation. Rechargez /availability et proposez immédiatement les créneaux voisins à votre client.
Le client existe déjà dans CleanPlus : est-il dupliqué ?
Non. Il est retrouvé par e-mail puis par SIRET, et ses informations ne sont jamais modifiées par l'API.
Comment transmettre photos et vidéos ?
Hébergez-les chez vous (URL https accessible) et passez-les dans attachments : elles sont rattachées aux documents du site, visibles par l'équipe.
Le prix est-il calculé par l'API ?
Non en v1 : votre simulateur calcule le prix (à partir de /services si besoin) et vous pouvez l'indiquer dans notes. Le paiement en ligne reste géré par votre tunnel.
Puis-je appeler l'API depuis le navigateur ?
Techniquement oui (CORS ouvert), mais la clé serait exposée. Passez par votre serveur ou une fonction serverless.
Versions
- v1.0 — octobre 2026 : créneaux libres, prestations, réservations (création, lecture, modification, annulation), webhooks signés, OpenAPI 3.1.
- Toute évolution incompatible fera l'objet d'une nouvelle version (
/v2) ; la v1 restera disponible.
Une question technique ? Écrivez à contact@cleanplus.app.