Resumen
| URL base | https://multilistado.mx/api/v1 |
| Formato | JSON (UTF-8). En POST/PUT envía Content-Type: application/json |
| Autenticación | Authorization: Bearer pbm_<prefijo>_<secreto> |
| Permisos | read (todas las consultas) · write (crear, editar, publicar, crear leads) |
| Límite | 600 solicitudes cada 10 minutos por key |
| Descripción OpenAPI 3.0 | /api/v1/openapi.json · referencia interactiva |
Autenticación
- Entra a Portal → Más → API (requiere licencia verificada).
- En Crear key escribe una Etiqueta (p. ej. «Mi CRM»), elige Permisos: «Lectura y escritura» o «Solo lectura», y pulsa Generar.
- Copia la key: se muestra una sola vez. Tiene la forma
pbm_+ 8 caracteres +_+ 48 caracteres. Puedes revocarla en cualquier momento.
Envíala en la cabecera Authorization. Por seguridad no se aceptan keys en la URL (?api_key= responde 401) y la API no habilita CORS: llámala desde tu servidor, nunca desde el navegador de tus visitantes (la key quedaría expuesta). Para mostrar propiedades en un sitio sin programar usa el widget.
Gratis para agentes e inmobiliarias con licencia. Crea tu cuenta, verifica tu licencia y genera tu API key o tu widget en minutos.
Las keys de socios RESO (permiso partner) solo sirven en la RESO Web API; en /api/v1 responden 403.
Cliente mínimo
Guarda tu key en una variable de entorno (MULTILISTADO_API_KEY) y usa este pequeño cliente en los ejemplos siguientes. Cada ejemplo continúa el anterior.
export MULTILISTADO_API_KEY="pbm_..." # tu key
API=https://multilistado.mx/api/v1
curl -s "$API/me" -H "Authorization: Bearer $MULTILISTADO_API_KEY"// Node.js 18+ (archivo .mjs). Nunca en el navegador: expondría tu key.
const API = 'https://multilistado.mx/api/v1';
async function api(method, path, body) {
const res = await fetch(API + path, {
method,
headers: { Authorization: `Bearer ${process.env.MULTILISTADO_API_KEY}`, ...(body ? { 'Content-Type': 'application/json' } : {}) },
body: body ? JSON.stringify(body) : undefined
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(`${res.status} ${data.message || data.error || ''} ${(data.errors || []).join(' ')}`);
return data;
}
const me = await api('GET', '/me');
console.log(me.name, me.scopes);<?php
// PHP 7.4+ con la extensión curl.
function mlx(string $method, string $path, ?array $body = null): array {
$ch = curl_init('https://multilistado.mx/api/v1' . $path);
$headers = ['Authorization: Bearer ' . getenv('MULTILISTADO_API_KEY'), 'Accept: application/json'];
if ($body !== null) {
$headers[] = 'Content-Type: application/json';
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => 30]);
$raw = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$data = json_decode((string) $raw, true) ?: [];
if ($code >= 400 || $raw === false) {
throw new RuntimeException($code . ' ' . ($data['message'] ?? $data['error'] ?? curl_error($ch)) . ' ' . implode(' ', $data['errors'] ?? []));
}
return $data;
}
$me = mlx('GET', '/me');
echo $me['name'], PHP_EOL;# Python 3.8+ con requests (pip install requests)
import os, requests
API = "https://multilistado.mx/api/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['MULTILISTADO_API_KEY']}"
def api(method, path, **kwargs):
r = session.request(method, API + path, timeout=30, **kwargs)
data = r.json() if r.content else {}
if not r.ok:
raise RuntimeError(f"{r.status_code} {data.get('message') or data.get('error')} {' '.join(data.get('errors', []))}")
return data
me = api("GET", "/me")
print(me["name"], me["scopes"])1. Buscar propiedades en el MLS
GET /properties devuelve propiedades publicadas de todo el Multilistado (y las tuyas), una página a la vez.
curl -s -G "$API/properties" -H "Authorization: Bearer $MULTILISTADO_API_KEY" \
--data-urlencode "city=Tijuana" --data-urlencode "operation=sale" --data-urlencode "type=house" \
--data-urlencode "min_price=2000000" --data-urlencode "sort=price_asc" --data-urlencode "limit=10"const qs = new URLSearchParams({ city: 'Tijuana', operation: 'sale', type: 'house', min_price: '2000000', sort: 'price_asc', limit: '10' });
const { pagination, content } = await api('GET', `/properties?${qs}`);
console.log(`${pagination.total} resultados, página ${pagination.page} de ${pagination.pages}`);
for (const l of content) console.log(l.title, '·', l.operations[0]?.formatted_amount, '·', l.agent.name);$qs = http_build_query(['city' => 'Tijuana', 'operation' => 'sale', 'type' => 'house', 'min_price' => 2000000, 'sort' => 'price_asc', 'limit' => 10]);
$page = mlx('GET', "/properties?$qs");
echo $page['pagination']['total'], " resultados", PHP_EOL;
foreach ($page['content'] as $l) {
echo $l['title'], ' · ', $l['operations'][0]['formatted_amount'] ?? '', ' · ', $l['agent']['name'], PHP_EOL;
}page = api("GET", "/properties", params={"city": "Tijuana", "operation": "sale", "type": "house",
"min_price": 2000000, "sort": "price_asc", "limit": 10})
print(page["pagination"]["total"], "resultados")
for l in page["content"]:
print(l["title"], "·", l["operations"][0]["formatted_amount"] if l["operations"] else "", "·", l["agent"]["name"])Respuesta (recortada, ilustrativa):
{
"pagination": { "page": 1, "pages": 3, "total": 27, "limit": 10 },
"content": [{
"id": "07e25392-…", "slug": "casa-en-venta-playas-de-tijuana", "url": "https://multilistado.mx/p/casa-en-venta-playas-de-tijuana",
"title": "Casa en venta en Playas de Tijuana", "property_type": "house", "status": "published",
"operations": [{ "type": "sale", "amount": 4350000, "currency": "MXN", "formatted_amount": "$4,350,000 MXN" }],
"bedrooms": 3, "bathrooms": 2.5, "parking": 2, "construction_m2": 180, "lot_m2": 200,
"location": { "address": null, "neighborhood": "Playas de Tijuana", "city": "Tijuana", "state": "Baja California", "lat": 32.519, "lng": -117.119, "exact": false },
"images": [{ "url": "https://…/fachada.jpg", "thumb": "https://…/fachada.jpg" }],
"agent": { "name": "…", "slug": "…", "phone": "…", "url": "https://multilistado.mx/agente/…" },
"agency": null, "updated_at": "2026-09-30T14:07:28.671Z", "published_at": "2026-09-30T14:04:19.703Z"
}]
}Filtros
| Parámetro | Descripción |
|---|---|
q | Texto libre (en español, sin acentos) |
operation | sale (tiene precio de venta) · rental (tiene precio de renta) |
type | house, apartment, land, office, commercial, warehouse, ranch, building, other. Repite el parámetro para varios: type=house&type=apartment |
state | Estado, nombre exacto: Baja California |
city, neighborhood | Ciudad y colonia (coincidencia parcial, sin acentos) |
min_price, max_price | Precio (el de venta con operation=sale, el de renta con rental; si no, cualquiera). No convierte monedas |
currency | MXN o USD: solo propiedades con precio en esa moneda |
min_bedrooms, min_bathrooms, min_parking | Mínimos |
min_construction, min_lot | m² mínimos de construcción y terreno |
features | Debe tener todas estas amenidades (etiquetas en español de /meta); repite el parámetro |
sw_lat, sw_lng, ne_lat, ne_lng | Rectángulo del mapa (coordenadas públicas aproximadas) |
lat, lng, radius_km | Radio alrededor de un punto |
updated_since | Fecha ISO 8601: solo cambios desde entonces (sincronización incremental) |
sort | newest (por omisión), price_asc, price_desc, updated, relevance (con q). Las destacadas van primero |
page, limit | Página (desde 1) y tamaño (por omisión 24, máximo 100) |
Qué incluye: propiedades publicadas de agentes verificados, y las tuyas en cualquier estado. No incluye importaciones con licencia de otros MLS, propiedades cuyo propietario no autorizó sitios de otros agentes (idx_opt_out) ni cuentas de prueba.
2. Ver una propiedad
GET /properties/{id} acepta el id (UUID) o el slug.
curl -s "$API/properties/casa-en-venta-playas-de-tijuana" -H "Authorization: Bearer $MULTILISTADO_API_KEY"const one = await api('GET', `/properties/${content[0].id}`);
console.log(one.title, one.url, one.images.length, 'fotos');$one = mlx('GET', '/properties/' . $page['content'][0]['id']);
echo $one['title'], ' ', $one['url'], PHP_EOL;one = api("GET", f"/properties/{page['content'][0]['id']}")
print(one["title"], one["url"], len(one["images"]), "fotos")3. Crear una propiedad
POST /properties (permiso write) crea la propiedad en tu inventario como borrador (draft). Obligatorios: title (mín. 5 caracteres), city, state y sale_price y/o rent_price.
curl -s -X POST "$API/properties" -H "Authorization: Bearer $MULTILISTADO_API_KEY" -H "Content-Type: application/json" -d '{
"title": "Casa en venta en Playas de Tijuana", "description": "Casa de 3 recámaras a dos cuadras de la playa.",
"property_type": "house", "sale_price": 4500000, "sale_currency": "MXN",
"bedrooms": 3, "bathrooms": 2.5, "parking": 2, "construction_m2": 180, "lot_m2": 200,
"city": "Tijuana", "state": "Baja California", "neighborhood": "Playas de Tijuana",
"lat": 32.5201, "lng": -117.1205, "features": ["Vista al mar", "Jardín"], "external_id": "crm-1042"
}'
# Guarda el "id" de la respuesta:
LISTING_ID="…"const created = await api('POST', '/properties', {
title: 'Casa en venta en Playas de Tijuana', description: 'Casa de 3 recámaras a dos cuadras de la playa.',
property_type: 'house', sale_price: 4500000, sale_currency: 'MXN',
bedrooms: 3, bathrooms: 2.5, parking: 2, construction_m2: 180, lot_m2: 200,
city: 'Tijuana', state: 'Baja California', neighborhood: 'Playas de Tijuana',
lat: 32.5201, lng: -117.1205, features: ['Vista al mar', 'Jardín'], external_id: 'crm-1042'
});
const id = created.id;
console.log(created.status, created.url); // draft https://multilistado.mx/p/…$created = mlx('POST', '/properties', [
'title' => 'Casa en venta en Playas de Tijuana', 'description' => 'Casa de 3 recámaras a dos cuadras de la playa.',
'property_type' => 'house', 'sale_price' => 4500000, 'sale_currency' => 'MXN',
'bedrooms' => 3, 'bathrooms' => 2.5, 'parking' => 2, 'construction_m2' => 180, 'lot_m2' => 200,
'city' => 'Tijuana', 'state' => 'Baja California', 'neighborhood' => 'Playas de Tijuana',
'lat' => 32.5201, 'lng' => -117.1205, 'features' => ['Vista al mar', 'Jardín'], 'external_id' => 'crm-1042',
]);
$id = $created['id'];
echo $created['status'], ' ', $created['url'], PHP_EOL;created = api("POST", "/properties", json={
"title": "Casa en venta en Playas de Tijuana", "description": "Casa de 3 recámaras a dos cuadras de la playa.",
"property_type": "house", "sale_price": 4500000, "sale_currency": "MXN",
"bedrooms": 3, "bathrooms": 2.5, "parking": 2, "construction_m2": 180, "lot_m2": 200,
"city": "Tijuana", "state": "Baja California", "neighborhood": "Playas de Tijuana",
"lat": 32.5201, "lng": -117.1205, "features": ["Vista al mar", "Jardín"], "external_id": "crm-1042",
})
listing_id = created["id"]
print(created["status"], created["url"])Campos que puedes enviar: title, description, title_en, description_en, property_type, sale_price, sale_currency, rent_price, rent_currency, rent_period (monthly, weekly, daily, yearly), bedrooms, bathrooms, half_bathrooms, parking, construction_m2, lot_m2, year_built, floors, features, address, neighborhood, city, municipality, state, postal_code, lat, lng, show_exact_address, idx_opt_out, video_url, virtual_tour_url, exclusive, shared_commission, internal_id, images y external_id (tu ID interno; se devuelve como source_id y es único por agente: si ya existe, la API responde 409 conflict con el id de esa propiedad para que la actualices con PUT). Los precios aceptan números o texto como "4,500,000". Las coordenadas se guardan exactas, pero se publican aproximadas salvo que show_exact_address sea true. Detalle completo en la referencia.
4. Actualizar
PUT /properties/{id} es parcial: envía solo lo que cambia.
curl -s -X PUT "$API/properties/$LISTING_ID" -H "Authorization: Bearer $MULTILISTADO_API_KEY" -H "Content-Type: application/json" \
-d '{"sale_price": 4350000, "title_en": "House for sale in Playas de Tijuana"}'const updated = await api('PUT', `/properties/${id}`, { sale_price: 4350000, title_en: 'House for sale in Playas de Tijuana' });
console.log(updated.operations[0].formatted_amount); // $4,350,000 MXN$updated = mlx('PUT', "/properties/$id", ['sale_price' => 4350000, 'title_en' => 'House for sale in Playas de Tijuana']);
echo $updated['operations'][0]['formatted_amount'], PHP_EOL;updated = api("PUT", f"/properties/{listing_id}", json={"sale_price": 4350000, "title_en": "House for sale in Playas de Tijuana"})
print(updated["operations"][0]["formatted_amount"])5. Imágenes
POST /properties/{id}/images agrega imágenes por URL pública (hasta 60 por llamada, en orden). La primera se usa como portada si no hay otra. Las imágenes se enlazan, no se copian: mantenlas en línea (tu servidor, CDN o almacenamiento público http/https). En API v1 no hay carga binaria de archivos; para subir fotos desde tu computadora usa el portal. Enviar images en un PUT reemplaza las imágenes por URL de la propiedad.
curl -s -X POST "$API/properties/$LISTING_ID/images" -H "Authorization: Bearer $MULTILISTADO_API_KEY" -H "Content-Type: application/json" \
-d '{"urls": ["https://cdn.tu-sitio.com/fotos/1042/fachada.jpg", "https://cdn.tu-sitio.com/fotos/1042/sala.jpg"]}'const media = await api('POST', `/properties/${id}/images`, { urls: ['https://cdn.tu-sitio.com/fotos/1042/fachada.jpg', 'https://cdn.tu-sitio.com/fotos/1042/sala.jpg'] });
console.log(media.media.length, 'imágenes');$media = mlx('POST', "/properties/$id/images", ['urls' => ['https://cdn.tu-sitio.com/fotos/1042/fachada.jpg', 'https://cdn.tu-sitio.com/fotos/1042/sala.jpg']]);
echo count($media['media']), ' imágenes', PHP_EOL;media = api("POST", f"/properties/{listing_id}/images", json={"urls": ["https://cdn.tu-sitio.com/fotos/1042/fachada.jpg", "https://cdn.tu-sitio.com/fotos/1042/sala.jpg"]})
print(len(media["media"]), "imágenes")6. Publicar y despublicar
| Llamada | Nuevo estado | Webhook |
|---|---|---|
POST /properties/{id}/publish | published | listing.published |
POST /properties/{id}/unpublish | draft | listing.unpublished |
DELETE /properties/{id} | withdrawn (se conserva, no se borra) | listing.unpublished |
curl -s -X POST "$API/properties/$LISTING_ID/publish" -H "Authorization: Bearer $MULTILISTADO_API_KEY"
# {"ok":true,"status":"published"}console.log(await api('POST', `/properties/${id}/publish`)); // { ok: true, status: 'published' }print_r(mlx('POST', "/properties/$id/publish"));print(api("POST", f"/properties/{listing_id}/publish"))Tu inventario completo (todos los estados, hasta 500, sin paginación): GET /my/properties (filtra con ?status=draft).
7. Crear un lead desde tu sitio
POST /leads (permiso write) registra una solicitud recibida en tu propio sitio o formulario. El lead se asigna a ti; property_id (UUID o slug) solo se vincula si administras esa propiedad. Recibes el aviso por correo como con cualquier lead. name es obligatorio.
curl -s -X POST "$API/leads" -H "Authorization: Bearer $MULTILISTADO_API_KEY" -H "Content-Type: application/json" \
-d "{\"name\": \"Ana López\", \"email\": \"ana@example.com\", \"phone\": \"+52 664 123 4567\", \"message\": \"Me interesa la casa.\", \"property_id\": \"$LISTING_ID\", \"source\": \"mi-sitio\"}"const lead = await api('POST', '/leads', { name: 'Ana López', email: 'ana@example.com', phone: '+52 664 123 4567', message: 'Me interesa la casa.', property_id: id, source: 'mi-sitio' });
console.log(lead.id, lead.listing_title);$lead = mlx('POST', '/leads', ['name' => 'Ana López', 'email' => 'ana@example.com', 'phone' => '+52 664 123 4567', 'message' => 'Me interesa la casa.', 'property_id' => $id, 'source' => 'mi-sitio']);
echo $lead['id'], ' ', $lead['listing_title'], PHP_EOL;lead = api("POST", "/leads", json={"name": "Ana López", "email": "ana@example.com", "phone": "+52 664 123 4567",
"message": "Me interesa la casa.", "property_id": listing_id, "source": "mi-sitio"})
print(lead["id"], lead["listing_title"])8. Tus leads
GET /leads devuelve tus últimos 500 leads (de tu sitio de Multilistado, el widget, portales y la API), del más nuevo al más antiguo.
curl -s "$API/leads" -H "Authorization: Bearer $MULTILISTADO_API_KEY"const { content: leads } = await api('GET', '/leads');
for (const l of leads.slice(0, 5)) console.log(l.created_at, l.name, l.kind, l.source, l.listing_title);$leads = mlx('GET', '/leads')['content'];
foreach (array_slice($leads, 0, 5) as $l) {
echo $l['created_at'], ' ', $l['name'], ' ', $l['kind'], ' ', $l['source'], PHP_EOL;
}leads = api("GET", "/leads")["content"]
for l in leads[:5]:
print(l["created_at"], l["name"], l["kind"], l["source"], l["listing_title"])Campos: id, listing_id, listing_title, listing_slug, name, email, phone, message, source (widget:<id>, api, tu etiqueta…), status, kind (info o tour = solicitud de visita), lang, interest, budget_min, budget_max, follow_up_at, tour_at, tour_status, showing_at, id_status (identificación del comprador: verified/pending), awaiting_id, created_at, updated_at. Para recibirlos al instante usa webhooks.
Otros endpoints
| Endpoint | Devuelve |
|---|---|
GET /me | Dueño de la key y permisos |
GET /meta | Tipos, estados de publicación, estados de México, amenidades y monedas (sin key) |
GET /agents | Directorio de agentes verificados públicos (hasta 1000) |
GET /agencies | Inmobiliarias activas (hasta 1000) |
GET /locations | Ciudades y estados con inventario publicado (top 200) |
GET /my/properties | Tu inventario en todos los estados |
Paginación
/properties responde pagination: { page, pages, total, limit }. Pide la siguiente página con page=2, page=3… hasta pages. Para sincronizar un inventario grande, guarda la hora de tu última sincronización y pide solo los cambios con updated_since=2026-09-30T00:00:00Z&sort=updated. Las listas /my/properties, /leads, /agents y /agencies no se paginan (tienen un tope fijo).
Errores
Los errores responden JSON con error (código estable) y, casi siempre, message:
{ "error": "unauthorized", "message": "Invalid or revoked API key." }| HTTP | error | Causa |
|---|---|---|
| 401 | unauthorized | Falta la key, es inválida o revocada, la cuenta no está verificada, o la mandaste en la URL |
| 403 | forbidden | La key no tiene permiso write, la propiedad no es tuya, o es una key de socio RESO |
| 404 | not_found | No existe o tu key no la puede ver |
| 409 | conflict | Ya tienes una propiedad con ese external_id; la respuesta incluye su id |
| 422 | validation | Datos inválidos; detalle en errors: [...] (mensajes en inglés) |
| 429 | rate_limited | Límite superado; espera los segundos de Retry-After |
| 500 | server_error | Error nuestro: reintenta más tarde y avísanos si persiste |
Límites
- 600 solicitudes cada 10 minutos por key. Cada respuesta trae
X-RateLimit-LimityX-RateLimit-Remaining; al pasarte recibes 429 conRetry-After(segundos). El conteo es aproximado (por proceso del servidor): trata 600 como tu presupuesto. - Páginas de hasta 100 propiedades; hasta 60 imágenes por llamada; cuerpo JSON de hasta 2 MB.
- Guarda en caché como máximo 12 horas y respeta los lineamientos de uso.
Qué no expone la API
- Comisiones compartidas (
shared_commission) y demás datos de comisión: puedes escribirlos, pero solo los ven agentes verificados dentro de Multilistado; nunca salen en la API, feeds ni RESO. - Coordenadas exactas y la dirección si el agente no eligió mostrarlas (
location.exact: false). - Datos privados del propietario, del comprador (identificaciones) y tokens internos de visitas.