Tu página web con clave publicable
Lista propiedades con filtros, recibe formularios y visitas, agenda citas y arma un buscador guiado.
Crea una clave publicable
En ByDomu, Configuración › API Keys › Nueva clave › Publicable. Sirve con cualquier plan pagado, sin solicitud, y solo alcanza lo público: portal:read, leads:write, booking:write, finder:write, events:write.
Puede ir en el navegador (la API responde con CORS abierto), pero todos tus visitantes comparten su límite: 300 peticiones y 60 escrituras por minuto. Para el inventario, pide desde tu servidor y guarda en caché; deja en el navegador solo los formularios. Una clave secreta en el navegador responde 403 SECRET_KEY_IN_BROWSER.
GET /propertiesGET /properties/{id}GET /properties/featuredGET /filtersGET /agentsGET /portalPOST /leadsPOST /leads/schedulePOST /booking/{slug}POST /finderPOST /eventsLista propiedades con filtros
Solo salen las propiedades activas y publicadas en el portal, nunca las notas internas ni los campos personalizados. Si la ubicación es aproximada, address y zipCode llegan en null y las coordenadas redondeadas.
// Clave publicable: puede ir en el navegador. Mejor aún, en tu servidor con caché.
const API = "https://api.bydomu.com/v1";
const KEY = "domu_pk_live_XXXXXXXX";
const params = new URLSearchParams({
transactionType: "RENT", // SALE | RENT | SALE_OR_RENT
type: "APARTMENT",
city: "Medellín", // coincidencia parcial
priceMax: "4000000",
bedroomsMin: "2",
hasPetFriendly: "1", // los has* se mandan solo cuando aplican
sortBy: "price", // price | createdAt | area
sortOrder: "asc",
pageSize: "12", // máximo 48
});
const res = await fetch(`${API}/properties?${params}`, { headers: { "X-API-Key": KEY } });
const { data, meta } = await res.json(); // meta: { total, page, pageSize, totalPages }Recibe el formulario de contacto
Crea o actualiza el contacto con su propiedad y sus UTM, y suma la analítica del portal. Un 400 trae en palabras qué campo falló: muéstralo en el formulario.
await fetch("https://api.bydomu.com/v1/leads", {
method: "POST",
headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
body: JSON.stringify({
firstName: "Laura", // obligatorio (2-100)
phone: "+573000000001", // obligatorio (7-20)
email: "[email protected]",
message: "¿Sigue disponible?",
propertyId: "clx1prop001", // en la ficha de una propiedad
interestedIn: "RENT", // SALE | RENT | SALE_OR_RENT
utmSource: new URLSearchParams(location.search).get("utm_source") ?? undefined,
}),
});
// 201 → { "data": { "success": true } }Si escuchas webhooks: el formulario dispara lead.submitted, pero hoy ese evento solo trae la respuesta de la API; los datos de la persona llegan en contact.created.
Visitas y citas
POST /leads/schedule registra una solicitud de visita con fecha y franja preferidas; no crea la cita: la inmobiliaria la confirma después.
await fetch("https://api.bydomu.com/v1/leads/schedule", {
method: "POST",
headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
body: JSON.stringify({
firstName: "Laura",
phone: "+573000000001",
propertyId: "clx1prop001", // obligatorio
preferredDate: "2026-10-04",
preferredTime: "morning", // morning | afternoon | evening
}),
});Para agendar de verdad, usa la agenda en línea de cada asesor: sus horas libres y la cita en un espacio libre (queda CONFIRMED si la página confirma sola; si no, SCHEDULED). Estas tres respuestas vienen doblemente envueltas: { data: { data: … } }.
// 1. Asesores con agenda en línea (cada uno con su slug)
const agents = await get("/booking/agents");
// 2. Horas libres de un mes. OJO: month va de 0 (enero) a 11 (diciembre)
const { data: { data: availability } } = await get("/booking/carlos-rodriguez/availability?month=9&year=2026");
// availability = { configured: true, availableDates: [{ date: "2026-10-05", slots: ["09:00", "10:00"] }] }
// 3. Agendar en un espacio libre
const res = await post("/booking/carlos-rodriguez", {
firstName: "Laura",
phone: "+573000000001",
date: "2026-10-05",
startTime: "09:00",
type: "SHOWING",
propertyId: "clx1prop001",
});
// 201 → { "data": { "data": { "appointmentId": "…", "status": "CONFIRMED" | "SCHEDULED", "ownerNotified": … } } }Arma un buscador guiado
Pregunta paso a paso qué busca la persona y manda todo junto al final: queda como contacto y la respuesta trae hasta 12 propiedades que encajan, de mejor a peor, con el puntaje (0-100) y el detalle del cálculo.
const res = await post("/finder", {
transactionType: "SALE", // SALE | RENT
propertyType: "APARTMENT",
city: "Bogotá",
neighborhood: "Chapinero",
budgetMin: 250000000,
budgetMax: 450000000,
minBedrooms: 2,
amenities: ["parking", "gym"],
firstName: "Laura",
phone: "+573000000001",
subscribedToAlerts: true,
});
// 201 → { "data": { "contact": { "id": "…" }, "total": 7,
// "properties": [{ "id": "…", "title": "…", "price": 450000000, "matchScore": 87,
// "breakdown": [{ "criterio": "presupuesto", "puntos": 30, "estado": "match" }], … }] } }Cuenta visitas y clics
Con events:write, tu web alimenta la analítica del portal en ByDomu. La respuesta es inmediata y no confirma que el evento se haya guardado.
await post("/events", { type: "PROPERTY_VIEW", propertyId: "clx1prop001", referrer: document.referrer });
// PAGE_VIEW | PROPERTY_VIEW | SEARCH | LEAD_FORM | WHATSAPP_CLICK | CALL_CLICK
// SCHEDULE_CLICK | SHARE_CLICK | FINDER_SUBMIT → 200 { "data": { "success": true } }Si algo falla
- 400Un campo no cumple el esquema (nombre muy corto, teléfono inválido, pageSize mayor a 48, month fuera de 0-11).
- 403Usaste una publicable en un endpoint privado (el mensaje dice cuáles puede usar) o una secreta desde el navegador.
- 404La propiedad o el slug de agenda no es de tu inmobiliaria.
- 429Tus visitantes agotaron el límite de la clave: sirve el inventario desde una caché.