ByDomuDevelopers

Your website with a publishable key

List properties with filters, receive forms and visit requests, book appointments and build a guided finder.

GuidePublishable keyNo approval

Create a publishable key

In ByDomu, Settings › API Keys › New key › Publishable. It works on any paid plan, with no request, and only reaches public data: portal:read, leads:write, booking:write, finder:write, events:write.

It can live in the browser (the API answers with open CORS), but all your visitors share its limit: 300 requests and 60 writes per minute. For listings, fetch from your server and cache; leave only the forms in the browser. A secret key in the browser gets 403 SECRET_KEY_IN_BROWSER.

What forEndpoint
List with filtersGET /properties
DetailGET /properties/{id}
FeaturedGET /properties/featured
Filter optionsGET /filters
AgentsGET /agents
Portal settingsGET /portal
Contact formPOST /leads
Visit requestPOST /leads/schedule
Online bookingPOST /booking/{slug}
Guided finderPOST /finder
Portal analyticsPOST /events

List properties with filters

Only active properties published on the portal come back, never internal notes or custom fields. When the location is approximate, address and zipCode arrive as null and coordinates are rounded.

// 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 }
Careful with booleans. On this endpoint the has* filters and includeTotal count as true with any non-empty value, even “false”: to skip a filter, don’t send it. To walk everything without counting the total, paginate with cursor (the id of the last property).

Receive the contact form

It creates or updates the contact with its listing and UTMs, and adds to the portal analytics. A 400 says in words which field failed: show it on the form.

JavaScript
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 } }

If you listen to webhooks: the form fires lead.submitted, but today that event only carries the API response; the person’s data arrives in contact.created.

Visits and bookings

POST /leads/schedule records a visit request with a preferred date and time slot; it doesn’t create the appointment: the agency confirms it later.

JavaScript
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
  }),
});

To actually book, use each agent’s online booking: their free slots and the appointment in a free slot (it’s CONFIRMED if the page auto-confirms; otherwise SCHEDULED). These three responses come double-wrapped: { data: { data: … } }.

JavaScript
// 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": … } } }

Build a guided finder

Ask step by step what the person is looking for and send it all at the end: it’s saved as a contact and the response carries up to 12 matching properties, best first, with the score (0-100) and the breakdown.

JavaScript
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" }], … }] } }

Count visits and clicks

With events:write, your site feeds the portal analytics in ByDomu. The response is immediate and doesn’t confirm the event was stored.

JavaScript
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 } }

If something fails

  • 400A field doesn’t match the schema (name too short, invalid phone, pageSize over 48, month outside 0-11).
  • 403You used a publishable key on a private endpoint (the message lists what it can use) or a secret key from a browser.
  • 404The property or booking slug doesn’t belong to your agency.
  • 429Your visitors used up the key’s limit: serve listings from a cache.