API & Webhooks · Documentation

Construisez sur Utiliz

Une API REST pour lire et écrire les données de votre compte — catalogue, disponibilités, clients, réservations — et des webhooks signés pour être notifié en temps réel. Votre site vitrine, vos automatisations et vos outils métier parlent directement à Utiliz.

$ curl https://utili-z.fr/api/v1/me \
    -H "Authorization: Bearer utz_live_votre_cle"

{
  "data": {
    "company_name": "Locations Dupont",
    "full_name": "Antoine Dupont",
    "email": "contact@exemple.fr",
    "scopes": "read"
  }
}
Base URLhttps://utili-z.fr/api/v1

Démarrer

Votre premier appel en trois étapes

1

Créez une clé API

Dans votre compte Utiliz : Paramètres → API & intégrations. La clé n'est affichée qu'une fois — stockez-la comme un mot de passe.

2

Testez-la

Appelez GET /v1/me avec le header Authorization (exemple ci-dessus). Si votre entreprise s'affiche, tout est en place.

3

Branchez vos outils

Lisez le catalogue, créez des réservations, ou abonnez un webhook pour réagir en temps réel.

Sécurité

Authentification

Chaque requête porte votre clé dans le header Authorization: Bearer utz_live_…. Une clé a l'un de ces deux niveaux de permission, choisi à sa création :

readLecture seule — catalogue, disponibilités, clients, réservations, webhooks.
read_writeLecture et écriture — peut aussi créer des clients, des réservations et des webhooks.

Clé manquante ou révoquée → 401 unauthorized. Écriture avec une clé en lecture seule → 403 forbidden. Abonnement Utiliz inactif → 403 subscription_expired.

Une clé ne vit que côté serveur

Ne placez jamais une clé dans le JavaScript d'une page web, une app mobile ou un dépôt Git : elle y serait lisible par n'importe qui. En cas de doute, révoquez-la depuis Paramètres → API & intégrations et créez-en une nouvelle. Une clé par usage (« Site vitrine », « Zapier »…) permet de révoquer chirurgicalement.

Référence

Conventions

RéponsesToujours du JSON. Les listes : { "data": [...], "next_cursor": "..." | null } — repassez next_cursor en query param ?cursor= pour la page suivante (?limit= 1-100, défaut 50).
ErreursFormat unique { "error": { "code", "message" } }. Codes principaux : invalid_parameter, not_found, insufficient_stock, rate_limited, internal_error.
DatesPériodes en YYYY-MM-DD (jours inclusifs : du 01 au 03 = 3 jours facturés). Horodatages en ISO 8601.
Limites120 requêtes par minute et par clé. Au-delà : 429 rate_limited — attendez la minute suivante.
IdempotenceSur les POST, envoyez un header Idempotency-Key: <valeur-unique> : un retry réseau avec la même clé renvoie la réponse d'origine au lieu de créer un doublon (la réponse rejouée porte le header Idempotency-Replayed: true).

Référence

Endpoints

GET/v1/me

Identité du compte associé à la clé. Sert de test de connexion.

GET/v1/products

Votre catalogue : nom, description, catégorie, prix/jour, stock total, actif ou non.

$ curl "https://utili-z.fr/api/v1/products?limit=50" \
    -H "Authorization: Bearer utz_live_votre_cle"
GET/v1/availability

Disponibilité d'un produit sur une période. Toute réservation non annulée qui chevauche la période bloque le stock — la même règle que dans l'application.

product_idUUID du produit (requis)
start_date / end_datePériode au format YYYY-MM-DD (requis)
$ curl "https://utili-z.fr/api/v1/availability?product_id=...&start_date=2026-08-01&end_date=2026-08-03" \
    -H "Authorization: Bearer utz_live_votre_cle"

{
  "data": {
    "product_id": "...",
    "product_name": "Enceinte active 500W",
    "is_active": true,
    "start_date": "2026-08-01",
    "end_date": "2026-08-03",
    "start_time": null,
    "end_time": null,
    "quantity_total": 6,
    "reserved": 1,
    "available": 5
  }
}

Ajoutez start_time et end_time (HH:MM, les deux ou aucun) pour une disponibilité précise au créneau — même définition que la création. Sans heures, la réponse est à la granularité de la journée (une location de 9h à 12h bloque le jour entier, calcul volontairement conservateur). La réponse renvoie start_time/end_time (null si aucune heure fournie).

GET/v1/clients
POST/v1/clients

Votre annuaire clients. En lecture, ?email= filtre sur l'adresse exacte (insensible à la casse) — pratique pour « chercher, sinon créer ». En création : name requis ; email, phone, company, address, notes optionnels.

$ curl -X POST https://utili-z.fr/api/v1/clients \
    -H "Authorization: Bearer utz_live_votre_cle" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: creation-client-042" \
    -d '{ "name": "Mairie de Bidart", "email": "events@bidart.fr" }'
GET/v1/reservations
POST/v1/reservations

En lecture, ?status= filtre par statut (pending, confirmed, in_progress, completed, cancelled). En création : plusieurs articles dans une même réservation, anti-double-réservation garanti côté serveur, et le prix est calculé par Utiliz — prix/jour × quantité × jours inclusifs, en tenant compte de la tarification spéciale éventuelle de l'article (tarifs samedi/dimanche/férié, remises longue durée) : mêmes montants que le dashboard, impossible d'envoyer un montant faux. Statut à la création : pending (défaut) ou confirmed. Le client : soit { "id": "..." } d'un client existant, soit { "name": "...", "email": "..." }.

Provenance. Chaque réservation porte un champ source dashboard, widget, api, woocommerce ou devis — posé à la création et jamais modifié ensuite. Ce que vous créez ici arrive en api, ce qui vous permet de reconnaître vos propres écritures quand vous relisez la liste.

C'est vous qui choisissez le statut, pas le loueur. Le réglage « Acceptation des demandes » de ses paramètres ne concerne que le widget de réservation. Envoyer confirmed crée donc une réservation ferme même chez un loueur qui valide habituellement chaque demande à la main : réservez-le aux réservations déjà payées ou déjà acceptées chez vous, et laissez le défaut pending partout ailleurs.

Locations à l'heure. Ajoutez start_time et end_time au format HH:MM — les deux ensemble, ou aucun des deux pour une location à la journée. Le prix passe alors au calcul horaire (jours pleins au prix/jour + heures restantes au prix/heure, plafonné au prix d'une journée). En lecture, les deux champs valent null sur une location à la journée. Le compte doit avoir activé les locations à l'heure et l'article porter un tarif horaire, sinon 400 hourly_disabled ou 400 missing_hourly_price.

$ curl -X POST https://utili-z.fr/api/v1/reservations \
    -H "Authorization: Bearer utz_live_votre_cle" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: resa-site-1042" \
    -d '{
      "start_date": "2026-08-01",
      "end_date": "2026-08-03",
      "client": { "name": "Mairie de Bidart", "email": "events@bidart.fr" },
      "items": [{ "product_id": "...", "quantity": 2 }]
    }'

{
  "data": {
    "reservation_group_id": "...",
    "status": "pending",
    "duration_days": 3,
    "total_price": 450,
    "items": [ ... ]
  }
}

Stock insuffisant sur la période → 409 insufficient_stock avec le détail (disponible vs demandé), et rien n'est créé.

À lire avant d'encaisser sur votre site. Interroger /v1/availability avant de faire payer ne protège de rien : deux clients qui règlent à la même seconde le dernier exemplaire passent tous les deux chez vous, et l'un des deux recevra un 409 ici — après encaissement. La seule autorité sur le stock est la création elle-même. Créez la réservation au moment du paiement et traitez le 409 (rembourser, ou proposer une autre date). Une réservation temporaire à durée de vie courte est prévue pour supprimer cette fenêtre.

GET/v1/reservations/{id}
PATCH/v1/reservations/{id}
POST/v1/reservations/{id}/cancel

{id} accepte aussi bien un reservation_group_id qu'un identifiant de ligne : l'opération porte toujours sur la réservation entière, articles compris.

Modifier. PATCH accepte notes, client, les dates et heures, et les quantités via items: [{ id, quantity }]. Tout changement de dates, d'heures ou de quantité revérifie le stock (409 insufficient_stock sinon) et recalcule le prix.

Le statut ne se modifie pas ici 400 status_not_patchable. Changer un statut a des effets de bord ; l'annulation a donc son propre endpoint. Seules les réservations pending et confirmed sont modifiables et annulables : une fois le matériel sorti, libérer le stock serait faux.

$ curl -X POST https://utili-z.fr/api/v1/reservations/{id}/cancel \
    -H "Authorization: Bearer utz_live_votre_cle" \
    -H "Content-Type: application/json" \
    -d '{ "reason": "Annulée par le client sur notre site" }'

{
  "data": {
    "reservation_group_id": "...",
    "status": "cancelled",
    "items": [ ... ]
  }
}

L'annulation est idempotente : rejouer l'appel renvoie 200 avec already_cancelled: true, jamais une erreur.

GET/v1/products/{id}
POST/v1/products
PATCH/v1/products/{id}
GET/v1/categories

Synchronisez votre catalogue. PATCH est partiel : envoyez seulement daily_price et le reste ne bouge pas. La category se donne par son nom, et doit exister — listez-les avec GET /v1/categories. Elles se créent dans Utiliz : elles portent une couleur et une icône choisies, en fabriquer depuis une synchronisation produirait des catégories sans identité au milieu des vôtres.

Pas de suppression. Un article référencé par des réservations et des factures ne peut pas disparaître sans laisser des lignes orphelines : mettez is_active: false. Il sort du widget et des sélecteurs, l'historique reste lisible.

La tarification spéciale n'est pas modifiable ici (400 pricing_rules_not_settable). Tarifs par jour, saisons, paliers et minimum se règlent dans Utiliz — une synchronisation ne doit pas pouvoir les écraser par omission.

$ curl -X PATCH https://utili-z.fr/api/v1/products/{id} \
    -H "Authorization: Bearer utz_live_votre_cle" \
    -H "Content-Type: application/json" \
    -d '{ "daily_price": 150, "quantity_total": 6 }'
GET/v1/factures
GET/v1/factures/{id}
POST/v1/factures
POST/v1/factures/{id}/payments

Vous encaissez sur votre site ? Émettez la facture avec POST /v1/factures, puis déclarez le règlement avec POST /v1/factures/{id}/payments. Utiliz ne réencaisse rien : il enregistre l'argent déjà reçu, et en déduit le statut.

Ce que le serveur calcule, et que vous ne fournissez pas. Le numero (attribué au moment de l'insertion, jamais dupliqué même sur des créations simultanées), le status, et les agrégats subtotal / tax_amount / total, déduits de vos lignes. Les fournir renvoie une erreur explicite plutôt que d'être ignoré en silence. En revanche le total d'une ligne vous appartient : c'est ce qui permet de facturer un tarif négocié ou une tarification spéciale.

Suivi. Chaque facture porte un bloc payment avec paid, refunded et remaining — remboursements partiels déduits. Le statut suit les encaissements : en_attente acompte_payepayee.

$ curl -X POST https://utili-z.fr/api/v1/factures \
    -H "Authorization: Bearer utz_live_votre_cle" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: facture-cmd-1042" \
    -d '{
      "client_id": "...",
      "date_echeance": "2026-09-15",
      "items": [
        { "name": "Randonnée quad 2 h", "quantity": 2, "total": 180 },
        { "name": "Assurance", "type": "frais", "total": 20 }
      ]
    }'

$ curl -X POST https://utili-z.fr/api/v1/factures/{id}/payments \
    -H "Authorization: Bearer utz_live_votre_cle" \
    -H "Content-Type: application/json" \
    -d '{ "amount": 200, "method": "cb", "paid_at": "2026-08-12" }'

Le montant d'un encaissement est borné au reste dû (400 amount_exceeds_remaining), et une facture déjà soldée le refuse (409 already_paid). Le règlement déclenche facture.paid ou facture.acompte_paid, exactement comme un paiement en ligne.

Hors périmètre, volontairement. Une facture émise ne se modifie pas et ne se supprime pas par API — elle s'avoire. Les remboursements se font depuis Utiliz : leur chemin dépend du compte Stripe où vit la charge, et se tromper y coûte de l'argent réel.

GET/v1/webhooks
POST/v1/webhooks
DELETE/v1/webhooks/{id}

Gérez vos webhooks par API (abonnement programmatique). À la création : url (https uniquement) et events (tableau, voir ci-dessous). Le secret de signature n'est retourné qu'à la création — stockez-le. La liste ne l'expose jamais.

Temps réel

Webhooks sortants

Utiliz envoie un POST JSON à votre URL quand un événement se produit. Configurez vos webhooks dans Paramètres → API & intégrations (avec un bouton de test) ou par API.

reservation.createdUne réservation vient d'être créée (application ou API).
reservation.updatedUne réservation a été modifiée — dates, heures, quantités, client ou notes. Émis aussi quand la modification vient du dashboard Utiliz.
reservation.cancelledUne réservation a été annulée et son stock libéré. C'est le seul événement qui rend du matériel disponible : si vous n'en écoutez qu'un, écoutez celui-là.
devis.signedUn client a signé un devis en ligne.
contrat.signedUn client a signé un contrat de location.
facture.paidUne facture a été payée en ligne.

Enveloppe : { "id", "type", "created_at", "data" } — dédoublonnez sur id, un envoi peut être retenté. Répondez un code 2xx en moins de 5 secondes ; sinon Utiliz retente (jusqu'à 5 fois).

Vérifier la signature

Chaque envoi porte X-Utiliz-Signature: t=<unix>,v1=<hmac> : un HMAC-SHA256 de `${t}.${corps brut}` avec le secret du webhook. Vérifiez-le pour rejeter les faux appels :

const crypto = require('crypto')

function verifieSignatureUtiliz(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map(p => p.split('='))
  )
  const attendu = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex')
  const recent = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300
  return recent && crypto.timingSafeEqual(
    Buffer.from(parts.v1), Buffer.from(attendu)
  )
}

Sans code

Votre planning dans n'importe quel agenda

Utiliz publie votre planning en iCalendar, le format que lisent Google Agenda, Apple Calendar, Outlook et la plupart des outils de planning. Activez-le dans Paramètres → API & intégrations → Calendrier partagé, copiez l'adresse, collez-la dans votre agenda. Aucune ligne de code.

Pourquoi une adresse secrète plutôt qu'une clé API ? Parce qu'un agenda ne sait pas envoyer d'en-tête Authorization : il récupère une URL, point. Le secret est donc dans l'adresse, et se régénère si elle a circulé.

Ce que le flux contient — et ce qu'il ne contient pas. Un événement par réservation (un panier de cinq articles = un événement), avec le nom du client, le matériel, le créneau et le statut. Ni email, ni téléphone, ni adresse, ni montant : une adresse secrète voyage dans des journaux de serveurs, le flux se limite donc à ce qu'on accepterait de voir fuiter. Les réservations annulées en sont exclues.

Lecture seule, et le rafraîchissement appartient à votre agenda — Google relit typiquement toutes les quelques heures. Ce n'est pas du temps réel : pour ça, utilisez les webhooks.

Recette

Brancher une boutique WooCommerce

Vous vendez déjà vos locations sur votre site et vous encaissez chez vous ? Utiliz n'a pas besoin de remplacer votre tunnel : il lui suffit d'apprendre ce qui s'y passe. Le principe vaut pour n'importe quel site — WooCommerce sert d'exemple parce que c'est le cas le plus courant.

1. La table de correspondanceAssociez chaque produit WooCommerce à un article Utiliz et à une durée. C'est le seul vrai travail, et personne ne peut le deviner à votre place.
2. Au paiement, pas à la commandeBranchez-vous sur woocommerce_payment_complete, jamais sur la création de commande : un panier abandonné ne doit pas bloquer de stock.
3. Client puis réservationPOST /v1/clients (ou retrouvez-le avec GET /v1/clients?email=), puis POST /v1/reservations avec Idempotency-Key: woo-order-<id> — si WooCommerce rejoue le hook, Utiliz rejoue la réponse au lieu de créer un doublon.
4. Traitez le 409Le stock a pu partir entre-temps. Passez la commande dans un statut dédié et prévenez-vous : l'argent est déjà encaissé, quelqu'un doit décider (rembourser, ou proposer une autre date).
5. Écoutez en retourAbonnez-vous à reservation.cancelled pour refléter dans WooCommerce ce qui est annulé côté Utiliz.

Deux règles qui coûtent cher si on les ignore. L'appel sortant doit être asynchrone — unwp_remote_post synchrone dans le chemin du paiement ralentit votre caisse. Et WooCommerce n'a aucune notion de date de réservation : elle vient de votre extension de booking (WooCommerce Bookings, YITH, Bookly…), chacune la rangeant à sa façon. C'est là que se situe l'essentiel du travail d'intégration, pas dans les appels à Utiliz.

Vous n'avez rien à développer. Cette recette décrit ce qu'il faudrait écrire — mais Utiliz sait déjà recevoir vos commandes. Activez Paramètres → API & intégrations → Boutique WooCommerce, collez l'adresse et le secret dans WooCommerce → Réglages → Avancé → Webhooks, et c'est tout : aucun plugin, aucune ligne de code.

Utiliz vous montre ensuite les produits et les clés de dates lus dans vos vraies commandes — vous n'avez pas à connaître les métadonnées de votre extension, seulement à reconnaître vos dates dans une liste. Le développement sur mesure ci-dessus reste utile si votre tunnel n'est pas WooCommerce, ou si vous voulez maîtriser chaque étape.

No-code

Connecter Zapier ou Make

Pas besoin d'attendre une app dédiée : les webhooks Utiliz et l'API se branchent directement sur les modules génériques de Zapier et Make.

Recevoir les événements Utiliz (déclencheur)

Zapier : déclencheur « Webhooks by Zapier → Catch Hook » — copiez l'URL fournie et créez un webhook Utiliz vers cette URL. Make : module « Webhooks → Custom webhook », même principe. Chaque réservation créée, devis signé ou facture payée déclenche alors votre scénario (ajouter une ligne Google Sheets, prévenir sur Slack, créer une tâche…).

Agir sur Utiliz (action)

Zapier : action « Webhooks by Zapier → Custom Request » (POST) vers /v1/clients ou /v1/reservations, avec votre header Authorization. Make : module « HTTP → Make a request ». Exemple : un formulaire Typeform rempli → une réservation en attente dans Utiliz.

Sans code

Widget de réservation

Pas de développeur ? Le widget affiche votre catalogue, vérifie les disponibilités en direct et envoie les demandes de réservation dans votre Utiliz — en collant une seule ligne sur votre site (WordPress, Wix, Squarespace…) :

<script src="https://utili-z.fr/embed.js" data-utiliz="VOTRE_TOKEN" async></script>

Activez-le et récupérez votre code dans Paramètres → API & intégrations → Widget de réservation. Par défaut, chaque demande arrive en statut « Réservée » et attend votre confirmation — l'acceptation automatique est une option.

Une question d'intégration ?

Décrivez-nous votre cas d'usage — on vous répond avec un exemple concret. Et si vous n'avez pas encore de compte, l'essai est gratuit 14 jours.

Nous écrireCréer un compte