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¶
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¶
{
"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
}
addressest l'adresse telle que Google l'a reconnue : affichez-la pour que le visiteur vérifie qu'il s'agit bien de la sienne.linesse lit dans l'ordre ; affichezlabeletamount. Une ligne absente ne s'applique pas (pas de surcharge, pas de jour supplémentaire…).- Hors zone :
coveredvautfalse,zoneestnull, 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¶
{
"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 :