groomly

Branchez votre logiciel sur Groomly.

Une API REST en JSON pour réserver les ménages de vos logements, suivre chaque mission jusqu'aux photos horodatées, et des webhooks signés pour être prévenu à chaque étape. Conçue pour les logiciels de conciergerie, les PMS et les outils comme Zapier ou Make.

# Réserver un ménage depuis votre logiciel
curl -X POST https://groomly-app.com/api/v1/menages \
  -H "Authorization: Bearer gk_live_…" \
  -d '{ "logementId": 42, "date": "2026-10-14", "heure": "11:00" }'

201 Created
{ "donnees": { "id": 1087, "statut": "EN_ATTENTE",
               "montant": 65.0, "logement": { "nom": "Studio du lac" } } }

Démarrer

L'API est réservée aux comptes entreprise (conciergeries, agences, loueurs en société). Tout se passe en trois étapes.

  • Dans votre espace Groomly, onglet Développeur (en bas du menu, comptes entreprise), créez une clé. Elle s'affiche une seule fois : copiez-la dans votre logiciel.
  • Appelez GET /api/v1/compte avec cette clé pour vérifier qu'elle fonctionne.
  • Réservez un ménage avec POST /api/v1/menages, puis abonnez une adresse de webhook pour suivre la mission sans interroger l'API.

Adresse de base : https://groomly-app.com/api/v1. Tout est en JSON, en UTF-8, en HTTPS uniquement. Les dates sont au format ISO 8601, en heure de Paris ; les montants sont en euros TTC.

curl https://groomly-app.com/api/v1/compte \
  -H "Authorization: Bearer gk_live_…"
{
  "donnees": {
    "email": "contact@ma-conciergerie.fr",
    "nom": "Ma Conciergerie",
    "typeCompte": "PRO",
    "facturationMensuelle": true,
    "cle": { "id": 3, "nom": "StayCore", "prefixe": "gk_live_ab12cd34", "portee": "ECRITURE", "active": true },
    "documentation": "https://groomly-app.com/developpeurs"
  }
}

Authentification

Clé API

Chaque appel porte l'en-tête Authorization: Bearer <clé>. Une clé commence par gk_live_, fait 256 bits aléatoires, et n'est stockée chez Groomly que sous forme d'empreinte : personne, pas même Groomly, ne peut la relire. Elle agit strictement au nom du compte qui l'a créée.

Une clé a une portée : ECRITURE (par défaut, elle réserve et annule) ou LECTURE (elle consulte seulement). Un appel en écriture avec une clé en lecture reçoit 403 PORTEE_INSUFFISANTE.

Depuis l'espace Groomly, le compte peut limiter une clé à des adresses IP (adresses ou plages CIDR, IPv4 et IPv6) : un appel venu d'ailleurs reçoit 403 IP_NON_AUTORISEE. L'adresse retenue est celle de la sortie Internet de votre serveur ; si votre logiciel passe par un mandataire, déclarez l'adresse de sortie de celui-ci. Le compte consulte aussi, pour chaque clé, le journal de ses 50 derniers appels (date, route, résultat, adresse, durée) et le nombre d'appels des 24 dernières heures ; le journal est conservé 90 jours.

Gardez la clé côté serveur, jamais dans une page web ou une application mobile. Un appel en HTTP clair est refusé (403 HTTPS_REQUIS) : la clé n'y transiterait pas chiffrée.

Jeton court

Pour ne pas présenter la clé à chaque appel, échangez-la contre un jeton valable une heure. Le jeton s'utilise exactement comme la clé. Quand il expire, demandez-en un nouveau avec la clé : il n'y a pas de « refresh token » à conserver, la clé joue ce rôle. Révoquer la clé révoque immédiatement tous les jetons émis avec elle.

curl -X POST https://groomly-app.com/api/v1/jetons \
  -H "Authorization: Bearer gk_live_…"

{ "donnees": { "jeton": "eyJ…", "type": "Bearer", "expireDans": 3600, "expireLe": "2026-10-05T16:12:00+02:00" } }

Rotation et révocation

Depuis l'onglet Développeur, Régénérer crée une nouvelle clé et laisse l'ancienne valable 24 heures : le temps de remplacer la clé chez votre intégrateur sans aucune interruption. Révoquer coupe une clé sur-le-champ. Un compte peut avoir cinq clés actives au plus, par exemple une par logiciel : révoquer l'une ne touche pas les autres.

Conventions

Les réponses réussies enveloppent le résultat dans donnees. Une création renvoie 201, une lecture ou une mise à jour 200.

Erreurs

Une erreur renvoie un statut HTTP 4xx ou 5xx et un corps { "erreur": { "code", "message" } }. Le code est stable et sert à votre logique ; le message, en français, s'affiche à un humain.

StatutCodesSens
400DATE_INVALIDE, DATE_PASSEE, HEURE_INVALIDE, PRESTATION_INCONNUE, LOGEMENT_REQUIS, URL_INVALIDE…La demande est mal formée.
401NON_AUTHENTIFIE, CLE_INVALIDE, JETON_INVALIDEClé absente, inconnue, révoquée ou expirée.
402PAIEMENT_REQUIS, PAIEMENT_REFUSELe compte n'a pas de moyen de paiement automatique (facturation mensuelle ou carte enregistrée), ou la banque a refusé.
403PORTEE_INSUFFISANTE, HTTPS_REQUIS, IP_NON_AUTORISEE, COMPTE_PRO_INCOMPLET, MANDAT_PROPRIETAIREL'appel est compris mais pas permis dans cet état.
404LOGEMENT_INTROUVABLE, MENAGE_INTROUVABLE, WEBHOOK_INTROUVABLEL'objet n'existe pas sur ce compte.
409DEJA_ANNULE, STATUT_NON_ANNULABLE, DELAI_DEPASSE, PAIEMENT_EN_COURS, TROP_DE_CLESConflit avec l'état actuel.
429TROP_D_APPELSLimite de débit atteinte : respectez Retry-After.
503PAIEMENT_INDISPONIBLELe service de paiement ne répond pas : réessayez.

Limites de débit

300 appels par minute et par clé. Au-delà, 429 avec un en-tête Retry-After en secondes. Les tentatives d'authentification ratées sont limitées à 30 par minute et par adresse.

Idempotence

À la création d'un ménage, donnez la référence de la réservation dans votre système, dans le champ reference ou dans l'en-tête Idempotency-Key. Si l'appel est rejoué (coupure réseau, relance), Groomly renvoie le ménage déjà créé avec 200 au lieu d'en créer un second. Par sécurité, un second ménage le même jour pour le même logement est également refusé en doublon.

Référence

Compte

GET/api/v1/compte

Le compte de la clé, son mode de facturation et la clé elle-même (sans sa valeur). Idéal pour valider une clé que l'utilisateur vient de coller.

POST/api/v1/jetons

Un jeton d'une heure contre la clé (voir Jeton court).

Logements

GET/api/v1/logements

Tous les logements du compte, du plus récent au plus ancien. C'est l'identifiant id qu'il faut pour réserver.

GET/api/v1/logements/{id}
{
  "donnees": [{
    "id": 42, "nom": "Studio du lac", "type": "airbnb",
    "adresse": { "rue": "12 quai Perrière", "codePostal": "74000", "ville": "Annecy" },
    "surface": 45, "prestations": ["menage_courant", "linge"], "litsDouble": 1, "litsSimple": 1,
    "tarifEstime": 81.0, "calendrier": { "actif": false, "etat": null }, "dateCreation": "2026-09-02T10:14:00+02:00"
  }]
}

Tarifs

GET/api/v1/prestations

La grille publique : le ménage courant par tranche de surface (prix et durée), les options avec leur prix, le supplément d'urgence. De quoi afficher des cases à cocher avec le tarif dans votre logiciel. Prix en euros TTC ; linge se compte par lit déclaré sur le logement, grand_menage est un pourcentage de la base, urgence ne se demande pas : Groomly l'ajoute d'après la date.

{
  "donnees": {
    "devise": "EUR",
    "menageCourant": [
      { "surfaceMin": 1,  "surfaceMax": 25, "prix": 35.0, "dureeHeures": 1.0 },
      { "surfaceMin": 26, "surfaceMax": 40, "prix": 50.0, "dureeHeures": 1.5 },
      { "surfaceMin": 41, "surfaceMax": 60, "prix": 65.0, "dureeHeures": 2.0 },
      { "surfaceMin": 61, "surfaceMax": 80, "prix": 85.0, "dureeHeures": 2.5 },
      { "surfaceMin": 81, "surfaceMax": 100, "prix": 100.0, "dureeHeures": 3.0 },
      { "surfaceMin": 101, "surfaceMax": null, "prixParM2": 1.0, "surDevis": true }
    ],
    "options": [
      { "cle": "vitres", "libelle": "Vitres, baies vitrées et miroirs",
        "unite": "forfait", "prix": 10.0 },
      { "cle": "linge", "libelle": "Kit linge et lits faits",
        "unite": "par lit", "prixLitDouble": 10.0, "prixLitSimple": 6.0 },
      { "cle": "repassage", "libelle": "Repassage du linge de maison (30 min)",
        "unite": "forfait", "prix": 10.0 },
      { "cle": "exterieur", "libelle": "Terrasse, balcon, mobilier extérieur",
        "unite": "forfait", "prix": 6.0 },
      { "cle": "grand_menage", "libelle": "Grand ménage, nettoyage en profondeur",
        "unite": "pourcentage de la base", "taux": 0.3 }
    ],
    "urgence": { "cle": "urgence", "libelle": "Intervention sous 48 h",
                 "prix": 10.0, "automatique": true }
  }
}
GET/api/v1/logements/{id}/tarif?prestations=vitres,linge&date=2026-10-20

Le devis exact d'un ménage pour ce logement, sans rien réserver : la base selon la surface, chaque option, le supplément d'urgence si la date est sous 48 h, la remise de parrainage du compte si elle existe, et montant, ce qui sera facturé. Sans prestations, les options habituelles du logement ; sans date, pas de supplément d'urgence. Avec les mêmes paramètres, POST /api/v1/menages donnera le même montant.

{
  "donnees": {
    "logement": { "id": 42, "nom": "Studio du lac" }, "date": "2026-10-20",
    "surface": 45, "surDevis": false, "prestations": ["menage_courant", "vitres", "linge"],
    "base": 65.0,
    "options": [
      { "cle": "vitres", "libelle": "Vitres, baies vitrees et miroirs", "prix": 10.0 },
      { "cle": "linge", "libelle": "Kit linge & lits faits - 1 lit double, 1 lit simple", "prix": 16.0 }
    ],
    "urgence": 0.0, "total": 91.0, "remiseParrainage": 0.0, "montant": 91.0, "dureeHeures": 2.0
  }
}

Ménages

POST/api/v1/menages

Réserve un ménage. Même parcours que dans l'application : grille tarifaire, remise de parrainage le cas échéant, paiement par facture mensuelle ou prélèvement sur la carte enregistrée, puis la mission est proposée aux Grooms.

ChampTypeDétail
logementIdentier, requisUn logement du compte.
dateAAAA-MM-JJ, requisLe jour du ménage, aujourd'hui ou plus tard. Sous 48 h, le supplément « urgence » de la grille s'applique.
heureHH:MMHeure de passage souhaitée, 06:00 à 20:00. Par défaut 11:00.
prestationslistevitres, linge, repassage, exterieur, grand_menage. Le ménage courant est toujours compris. Absent : les prestations du logement. Prix et libellés : GET /api/v1/prestations ; devis avant de réserver : GET /api/v1/logements/{id}/tarif. En lecture, un ménage peut aussi porter urgence : le supplément ajouté automatiquement sous 48 h.
referencetexte ≤ 120Votre identifiant de réservation : rend l'appel idempotent et se retrouve dans chaque événement.
curl -X POST https://groomly-app.com/api/v1/menages \
  -H "Authorization: Bearer gk_live_…" -H "Content-Type: application/json" \
  -d '{ "logementId": 42, "date": "2026-10-14", "heure": "11:00",
        "prestations": ["linge"], "reference": "HMABCDEFGH" }'

201 Created
{ "donnees": { "id": 1087, "reference": "HMABCDEFGH", "statut": "EN_ATTENTE", "statutPaiement": "MENSUEL",
               "montant": 81.0, "logement": { "id": 42, "nom": "Studio du lac" }, "date": "2026-10-14", "heure": "11:00", … } }
GET/api/v1/menages

Les ménages du compte, du plus récent au plus ancien. Filtres : depuis et jusqua (AAAA-MM-JJ, sur la date du ménage), statut (EN_ATTENTE, CONFIRME, EN_COURS, TERMINE, ANNULE), logementId, limite (100 par défaut, 200 au plus).

GET/api/v1/menages/{id}

Un ménage avec son Groom, ses photos et leurs preuves (voir Objets).

POST/api/v1/menages/{id}/annuler

Annule un ménage, aux mêmes conditions que dans l'application : impossible une fois commencé, et impossible en ligne à moins de 24 h d'un ménage confirmé par un Groom (409 DELAI_DEPASSE). Un ménage déjà payé est remboursé.

Webhooks

GET/api/v1/webhooks
POST/api/v1/webhooks

Abonne une adresse https. Corps : { "url", "evenements": [ … ] } ; sans evenements, tous sont envoyés. La réponse contient le secret de signature, une seule fois ; Groomly le conserve chiffré. Cinq webhooks par compte au plus ; ils se gèrent aussi depuis l'onglet Développeur de votre espace.

DELETE/api/v1/webhooks/{id}

Recevoir les événements

Groomly envoie une requête POST JSON à votre adresse à chaque étape d'un ménage. Répondez 2xx en moins de 10 secondes ; traitez le contenu ensuite, hors de la requête.

ÉvénementQuand
menage.reserveLe ménage est payé (ou mis sur la facture mensuelle) et proposé aux Grooms.
menage.confirmeUn Groom l'a accepté : le ménage porte son nom.
menage.commenceLe Groom est sur place et a commencé.
menage.termineLe ménage est fait : les photos et vidéos, avec heure serveur et empreinte SHA-256, sont dans l'objet.
menage.annuleAnnulé par vous, par le calendrier, ou faute de Groom à temps.
test.pingEnvoyé depuis le bouton « Tester » de l'espace, avec un ménage d'exemple.
POST https://votre-logiciel.fr/webhooks/groomly
Content-Type: application/json; charset=utf-8
Groomly-Event: menage.termine
Groomly-Delivery: evt_8f0c…
Groomly-Signature: t=1759672800,v1=5f1e…

{ "id": "evt_8f0c…", "type": "menage.termine", "date": "2026-10-14T13:02:11+02:00",
  "donnees": { "menage": { "id": 1087, "reference": "HMABCDEFGH", "statut": "TERMINE",
               "photos": [ { "url": "…", "type": "PHOTO", "dateServeur": "2026-10-14T12:58:40+02:00",
                             "sha256": "3f9a2c…", "tache": "Cuisine" } ], … } } }

Vérifier la signature

L'en-tête Groomly-Signature contient l'horodatage t (secondes Unix) et v1, le HMAC-SHA256 en hexadécimal, calculé avec votre secret sur la chaîne t + "." + corps, où corps est le texte brut reçu, octet pour octet. Comparez en temps constant, et rejetez tout événement dont t a plus de cinq minutes : c'est ce qui empêche de rejouer un ancien message.

// Node.js
const crypto = require('crypto');
function verifier(secret, entete, corpsBrut) {
  const p = Object.fromEntries(entete.split(',').map(x => x.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(p.t)) > 300) return false;
  const attendu = crypto.createHmac('sha256', secret).update(p.t + '.' + corpsBrut).digest('hex');
  return attendu.length === p.v1.length && crypto.timingSafeEqual(Buffer.from(attendu), Buffer.from(p.v1));
}
# Python
import hmac, hashlib, time
def verifier(secret, entete, corps_brut):
    p = dict(x.split('=') for x in entete.split(','))
    if abs(time.time() - int(p['t'])) > 300: return False
    attendu = hmac.new(secret.encode(), f"{p['t']}.{corps_brut}".encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(attendu, p['v1'])
Chaque livraison porte un identifiant Groomly-Delivery (le même que id dans le corps). Gardez-le : une reprise renvoie le même identifiant, votre traitement peut ainsi rester idempotent.

Reprises

Sans réponse 2xx, Groomly réessaie après 1 minute, 10 minutes, 1 heure, 6 heures puis 24 heures, et abandonne ensuite. Après 30 échecs consécutifs, le webhook est désactivé ; il se réactive d'un clic dans l'onglet Développeur, où chaque livraison et sa réponse sont visibles. Les événements ne sont jamais envoyés dans un ordre garanti : fiez-vous au statut de l'objet, pas à l'ordre d'arrivée.

Objets

Ménage

ChampDétail
id, referenceIdentifiant Groomly ; votre référence, si vous l'avez donnée.
logement{ id, nom }
date, heure, creneauJour (AAAA-MM-JJ), heure de passage, matin ou aprem.
statutEN_ATTENTE (proposé aux Grooms) → CONFIRME → EN_COURS → TERMINE, ou ANNULE.
statutPaiementMENSUEL (facture mensuelle), EN_COURS, PAYE, REFUSE, REMBOURSE, ANNULE.
prestations, duree, montantPrestations, durée estimée en heures, montant TTC en euros.
groom{ nom, photoUrl } dès qu'un Groom a accepté, sinon null.
photosListe de { url, type (PHOTO|VIDEO), dateServeur, sha256, tache }. Prises dans l'application, sur place ; dateServeur est l'heure de réception sur nos serveurs et sha256 l'empreinte du fichier reçu, vérifiable par GET /api/preuve/verifier?sha256=….
avis{ note, commentaire } laissé par le propriétaire, sinon null.
origineAPI, CALENDRIER (créé par le calendrier iCal du logement) ou MANUEL.

Logement

id, nom, type, adresse { rue, codePostal, ville }, surface, prestations, litsDouble, litsSimple, tarifEstime, calendrier { actif, etat }, dateCreation.

Support

Une question, un cas qui n'entre pas dans ces pages, une intégration à construire ensemble : écrivez à contact@groomly-app.com en précisant le préfixe de votre clé (gk_live_ab12cd34, jamais la clé complète). L'API est versionnée dans son chemin : /api/v1 restera stable, et tout changement incompatible vivra dans une /api/v2.

Créer ma clé dans mon espace