Aller au contenu

Référence de l'API site web

Deux appels suffisent : lire le catalogue, puis créer le brouillon de réservation. Un troisième, facultatif, calcule le prix exact pour une adresse. Ils sont anonymes (aucune clé ni jeton) ; l'accès est réservé aux sites que l'entreprise a autorisés dans Rolaf (voir Activer l'intégration).

Généralités

Adresse de l'API https://api.rolaf.ca
Format JSON (Content-Type: application/json pour les envois)
Domaine en-tête Domain: <identifiant> (ou paramètre ?domain=<identifiant>)
CORS ouvert : les appels se font directement depuis le navigateur
Authentification aucune ; contrôle de l'en-tête Origin

Contrôle de l'origine. Le navigateur envoie l'en-tête Origin (par exemple https://www.example.com) ; il doit figurer dans la liste des sites autorisés, sinon l'API répond 403 origin_not_allowed. Un appel fait depuis un serveur, sans en-tête Origin, est accepté tant que l'intégration est activée.

Limites. Par adresse IP : 60 lectures du catalogue par minute, 30 devis et 20 brouillons par 10 minutes. Au-delà : 429 avec {"error": "rate_limit", "retry_after": <secondes>}.

Lire le catalogue

GET /website/catalog
Domain: exemple

Renvoie les zones de livraison et les matières offertes, chacune avec les tailles de conteneur possibles, le prix de base du couple matière/taille et le prix du transport par zone. Matières triées par nom, tailles par ordre croissant.

{
  "domain": "exemple",
  "currency": "CAD",
  "portal_url": "https://portal-exemple.rolaf.ca/",
  "zones": [
    { "zone_number": 1, "name": "Centre" },
    { "zone_number": 2, "name": "Couronne" }
  ],
  "materials": [
    {
      "id": "64b0c0ffee0000000000a001",
      "name": "Résidus de construction",
      "description": "",
      "sizes": [
        {
          "id": "64b0c0ffee0000000000b010",
          "name": "10",
          "description": "10 verges cubes",
          "price": 460,
          "days_included": 7,
          "days_included_by_type": { "individual": 10, "business": 7 },
          "weight_included_kg": 2000,
          "notes": "Rien au-dessus des rebords",
          "fuel_surcharge_percent": 4,
          "transport_by_zone": [
            { "zone_number": 1, "price": 0 },
            { "zone_number": 2, "price": 75 }
          ]
        }
      ]
    }
  ]
}
Champ Description
currency devise des prix (CAD)
portal_url adresse du portail client de l'entreprise
materials[].id identifiant de la matière, à renvoyer dans material_id
materials[].name, description libellés tels que saisis dans Rolaf
sizes[].id identifiant de la taille, à renvoyer dans size_id
sizes[].name, description libellés de la taille (souvent le volume en verges cubes)
sizes[].price prix de base, sans transport, surcharge carburant ni taxes (null si non saisi)
zones[] zones de livraison : zone_number et name
sizes[].days_included jours de location inclus (valeur générale)
sizes[].days_included_by_type jours inclus pour un particulier (individual) et une entreprise (business)
sizes[].weight_included_kg poids inclus, en kg (null si non saisi)
sizes[].notes consignes de remplissage à afficher au client (chaîne vide s'il n'y en a pas)
sizes[].fuel_surcharge_percent surcharge carburant, en % du prix de base (null si aucune)
sizes[].transport_by_zone[] prix du transport vers chaque zone (zone_number, price) ; une zone absente n'a pas de prix pour cette taille

Calculer le prix pour une adresse

POST /website/quote
Domain: exemple
Content-Type: application/json
{
  "material_id": "64b0c0ffee0000000000a001",
  "size_id": "64b0c0ffee0000000000b010",
  "address": "1000 rue Exemple, Montréal",
  "delivery_date": "2026-11-02",
  "pickup_date": "2026-11-14",
  "type": "business"
}

Rolaf géolocalise l'adresse (inutile de le faire sur votre site), vérifie qu'elle est dans une zone de livraison et calcule le prix détaillé : location, transport de la zone, surcharge carburant, jours supplémentaires (si les deux dates sont données) et taxes.

Champ Type Description
material_id, size_id identifiants obligatoires, tirés du catalogue
address texte (300) adresse à géolocaliser
latitude, longitude nombres à la place de address, si votre site a déjà la position
delivery_date, pickup_date AAAA-MM-JJ pour compter les jours supplémentaires
type individual | business particulier ou entreprise (jours inclus différents) ; individual par défaut
lang fr | en langue des libellés ; fr par défaut
{
  "address": "1000 Rue Exemple, Montréal, QC H3A 0A0, Canada",
  "latitude": 45.501,
  "longitude": -73.574,
  "covered": true,
  "zone": { "zone_number": 1, "name": "Centre" },
  "currency": "CAD",
  "days_included": 7,
  "weight_included_kg": 2000,
  "price_per_day": 10,
  "notes": "Rien au-dessus des rebords",
  "lines": [
    { "code": "base", "label": "Location du conteneur", "amount": 460 },
    { "code": "transport", "label": "Transport, zone 1 (Centre)", "zone_number": 1, "amount": 0 },
    { "code": "fuel_surcharge", "label": "Surcharge carburant (4 %)", "percent": 4, "amount": 18.4 },
    { "code": "extra_days", "label": "Jours supplémentaires (5 × 10,00 $)", "days": 5, "unit_price": 10, "amount": 50 }
  ],
  "subtotal": 528.4,
  "taxes": [
    { "name": "TPS", "rate": 5, "amount": 26.42 },
    { "name": "TVQ", "rate": 9.975, "amount": 52.71 }
  ],
  "total": 607.53
}
  • address est l'adresse telle que Google l'a reconnue : affichez-la pour que le visiteur vérifie qu'il s'agit bien de la sienne.
  • lines se lit dans l'ordre ; affichez label et amount. Une ligne absente ne s'applique pas (pas de surcharge, pas de jour supplémentaire…).
  • Hors zone : covered vaut false, zone est null, il n'y a ni ligne de transport, ni taxes, ni total. Le portail refusera la réservation à cette adresse : invitez plutôt le visiteur à appeler.
  • Le prix est indicatif : le portail le recalcule et l'affiche avant la confirmation (un client existant peut avoir des tarifs personnalisés).

Créer le brouillon de réservation

POST /website/draft
Domain: exemple
Content-Type: application/json
{
  "type": "individual",
  "firstname": "Ana",
  "lastname": "Exemple",
  "email": "ana@example.com",
  "phone": "514-555-0100",
  "orders": [
    {
      "material_id": "64b0c0ffee0000000000a001",
      "size_id": "64b0c0ffee0000000000b010",
      "address": "1000 rue Exemple, Montréal, QC",
      "delivery_date": "2026-11-03",
      "delivery_notes": "Entrée par la ruelle"
    }
  ]
}

Seul orders est obligatoire, et dans chaque conteneur material_id et size_id. Tout le reste est facultatif : ce qui manque, le client le saisit sur le portail.

Client

Champ Type Description
type individual | business particulier ou entreprise
firstname, lastname texte (100) particulier
name texte (200) nom de l'entreprise
email courriel (200) sert aussi à reconnaître un client existant
phone texte (30) idem ; tout format nord-américain

Conteneurs (orders, de 1 à 20)

Champ Type Description
material_id identifiant obligatoire, tiré du catalogue
size_id identifiant obligatoire ; le couple doit exister dans le catalogue
address texte (300) adresse de livraison ; Rolaf la géolocalise si latitude et longitude manquent
latitude, longitude nombres position de l'adresse, par exemple celle renvoyée par /website/quote
delivery_date AAAA-MM-JJ jour de livraison ; le client choisit la plage horaire
delivery_datetime ISO 8601 jour et heure de livraison, à la place de delivery_date
pickup_date AAAA-MM-JJ jour de ramassage prévu
wait_on_site booléen le chauffeur attend sur place (chargement immédiat)
delivery_notes texte (1000) indications pour le chauffeur

Tout autre champ est refusé (400).

Réponse

{
  "token": "Qm8x…",
  "url": "https://portal-exemple.rolaf.ca/#/booking?draft=Qm8x…",
  "expires_date": "2026-11-05T14:12:03+00:00",
  "ttl_days": 7
}

Redirigez le visiteur vers url. Le lien est personnel (il porte ses coordonnées) : ne l'affichez pas publiquement. Il cesse de fonctionner après expires_date ou une fois la commande créée.

Erreurs

Code error Cause
400 domain_required ni en-tête Domain ni paramètre domain
400 (validation) champ inconnu, mauvais type ou format ; error_data.prop nomme le champ
403 website_booking_disabled aucun site autorisé pour ce domaine
403 origin_not_allowed l'origine de la page n'est pas dans la liste
400 address_required devis sans address ni coordonnées
422 address_not_found devis : adresse introuvable
422 unknown_size_material ce couple matière/taille n'est pas offert ; pour un brouillon, order_index désigne le conteneur
422 invalid_delivery_datetime delivery_datetime illisible ; order_index idem
429 rate_limit trop d'appels ; réessayer après retry_after secondes

Exemple complet

Une page minimale : liste des matières, des tailles, et bouton « Réserver ». Pour une version complète et mise en forme, voyez la démo demo-recybac.ubi2.ca (code source : afficher la source de la page).

<form id="reservation">
  <select id="matiere" required></select>
  <select id="taille" required></select>
  <input id="adresse" placeholder="Adresse de livraison">
  <input id="date" type="date">
  <p id="notes"></p>
  <button>Réserver</button>
</form>

<script>
  const API = "https://api.rolaf.ca";
  const HEADERS = { "Domain": "exemple" };
  let catalogue = [];

  const matiere = document.getElementById("matiere");
  const taille = document.getElementById("taille");

  function afficher_tailles() {
    const m = catalogue.find((x) => x.id === matiere.value);
    taille.innerHTML = m.sizes.map((s) =>
      `<option value="${s.id}">${s.name} vg³ — à partir de ${s.price} $</option>`
    ).join("");
    afficher_notes();
  }

  function afficher_notes() {
    const m = catalogue.find((x) => x.id === matiere.value);
    const s = m.sizes.find((x) => x.id === taille.value);
    document.getElementById("notes").textContent = s?.notes || "";
  }

  fetch(`${API}/website/catalog`, { headers: HEADERS })
    .then((r) => r.json())
    .then((data) => {
      catalogue = data.materials;
      matiere.innerHTML = catalogue.map((m) =>
        `<option value="${m.id}">${m.name}</option>`
      ).join("");
      afficher_tailles();
    });

  matiere.addEventListener("change", afficher_tailles);
  taille.addEventListener("change", afficher_notes);

  document.getElementById("reservation").addEventListener("submit", async (e) => {
    e.preventDefault();
    const commande = { material_id: matiere.value, size_id: taille.value };
    const adresse = document.getElementById("adresse").value.trim();
    const date = document.getElementById("date").value;
    if (adresse) commande.address = adresse;
    if (date) commande.delivery_date = date;
    const r = await fetch(`${API}/website/draft`, {
      method: "POST",
      headers: { ...HEADERS, "Content-Type": "application/json" },
      body: JSON.stringify({ orders: [commande] }),
    });
    const data = await r.json();
    if (r.ok) window.location = data.url;
    else alert("Réservation impossible pour le moment : " + data.error);
  });
</script>

Avec curl, depuis un serveur :

curl -s https://api.rolaf.ca/website/catalog -H "Domain: exemple"

curl -s https://api.rolaf.ca/website/draft -H "Domain: exemple" \
  -H "Content-Type: application/json" \
  -d '{"orders":[{"material_id":"…","size_id":"…","delivery_date":"2026-11-03"}]}'