Saltar al contenido

Para desarrolladores

API y webhooks

Lee y escribe las propiedades, los contactos y las citas de tu inmobiliaria en The Yellow Page desde tus propios sistemas, y recibe un aviso cuando algo cambia.

La API y los webhooks están disponibles para las inmobiliarias cuyo plan los incluye. Las claves las crea un dueño de la inmobiliaria en su panel, en la pestaña «Desarrolladores». Ver planes

URL base
https://api.yellowecuador.com/api/public/v1

Autenticación

Cada solicitud lleva una clave en la cabecera Authorization, con el esquema Bearer. La clave pertenece a una inmobiliaria: todo lo que lee o escribe es de esa inmobiliaria.

curl "https://api.yellowecuador.com/api/public/v1/me" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"
  • La clave se muestra una sola vez, al crearla. Guardamos solo su huella.
  • Úsala solo desde un servidor. No la pongas en una página web ni en una app: quien la vea puede usarla.
  • Una clave revocada o vencida deja de funcionar en la siguiente solicitud.

Permisos

  • listings:readLeer las propiedades.
  • listings:writeCrear, editar y publicar propiedades; añadir fotos.
  • leads:readLeer los contactos.
  • leads:writeEnviar contactos, cambiar su etapa y añadir notas.
  • appointments:readLeer las citas.
  • webhooks:manageCrear, cambiar y eliminar endpoints de webhook.

Convenciones

  • Todo es JSON, en UTF-8. Las fechas son instantes ISO 8601 en UTC.
  • Un id que no es de tu inmobiliaria responde 404, igual que uno que no existe.
  • Las listas devuelven «data» y «nextCursor». Pide la página siguiente con cursor=nextCursor; cuando es null no hay más. El parámetro limit va de 1 a 100 (25 si no lo envías).
  • Tu inmobiliaria puede hacer 120 solicitudes por minuto en total —todas las claves cuentan para la misma cifra, y una clave puede usar como máximo la mitad (al menos 30) para no dejar sin cupo a las demás—, salvo que tu contrato diga otra cifra. Las cabeceras RateLimit-Limit, RateLimit-Remaining y RateLimit-Reset te dicen cuántas quedan; al pasarte, la respuesta es 429 con Retry-After.
  • Las propiedades creadas por la API tienen su propio cupo, aparte del del propietario en el panel: 600 por hora por inmobiliaria (más si tu contrato sube el límite) y como máximo 500 borradores creados por la API (2000 con importación masiva). Al pasarte, la respuesta es 429, o 409 too_many_drafts.
  • En cualquier POST puedes enviar la cabecera Idempotency-Key. Si repites la solicitud con la misma clave y el mismo cuerpo en 24 horas, recibes la respuesta original y no se crea nada dos veces.
  • Un campo desconocido en el cuerpo o en la consulta es un error 400: nada se ignora en silencio.
  • La versión está en la ruta. Dentro de v1 solo añadimos cosas: campos nuevos, rutas nuevas, eventos nuevos.

Errores

Un error llega como application/problem+json. El último tramo de «type» es el código de la tabla; «detail» lo explica en inglés y, en los errores de validación, «errors» enumera cada campo.

{
  "type": "https://casa-ec.dev/problems/insufficient_scope",
  "title": "Forbidden",
  "status": 403,
  "detail": "This key does not hold the \"leads:write\" scope",
  "scope": "leads:write",
  "requestId": "…"
}
  • 401invalid_api_keyFalta la clave, está mal formada, revocada o vencida, o la inmobiliaria ya no tiene acceso a la API.
  • 403insufficient_scopeLa clave no tiene el permiso que esta ruta necesita (ver `scope` en la respuesta).
  • 403no_acting_ownerLa inmobiliaria no tiene un propietario activo en cuyo nombre actuar.
  • 404not_foundEse recurso no existe en esta inmobiliaria.
  • 400validationEl cuerpo o los parámetros no son válidos; `errors` enumera cada campo.
  • 400invalid_cursorEl cursor no pertenece a esta lista.
  • 429rate_limitedDemasiadas solicitudes (por inmobiliaria y por clave, o el cupo por hora de propiedades creadas); espere los segundos de `Retry-After`.
  • 409too_many_draftsLa inmobiliaria tiene 500 borradores creados por la API (2 000 con importación masiva); publique o elimine algunos primero.
  • 402plan_requiredEl plan no incluye esta función.
  • 402plan_limit_reachedSe alcanzó un límite del plan (propiedades activas).
  • 409invalid_transitionLa propiedad no puede pasar de su estado al solicitado.
  • 409version_conflictEl contacto cambió desde que se leyó `version`.
  • 409duplicate_contactReabrir el contacto duplicaría uno abierto.
  • 409idempotency_in_progressUna solicitud con esta Idempotency-Key sigue en curso.
  • 422idempotency_key_reusedEsta Idempotency-Key se usó con otra solicitud.
  • 422listing_incompleteA la propiedad le falta lo que exige la publicación; `errors` lo enumera.
  • 422unknown_geoProvincia, cantón o parroquia desconocidos o incoherentes.
  • 422not_a_member`ownerId` no es un miembro profesional activo de la inmobiliaria.
  • 422unsafe_urlLa URL no es una dirección https de internet público.
  • 422fetch_failedNo se pudo descargar la imagen.
  • 422not_an_imageEl archivo no es una imagen JPEG, PNG o WebP.
  • 422too_largeLa imagen supera los 10 MB.
  • 422media_limitUna propiedad admite 30 imágenes como máximo.
  • 503outbound_busyNuestra capacidad de salida está llena por un momento; reintenta tras los segundos de `Retry-After`.
  • 409endpoint_limitUna inmobiliaria admite 10 endpoints de webhook como máximo.

La clave y el documento

Comprueba una clave y descarga esta misma referencia en formato OpenAPI.

GET/me

La clave en uso, sus permisos y su límite de solicitudes

Cualquier clave válida.

curl

curl "https://api.yellowecuador.com/api/public/v1/me" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/me", {
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
  },
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 429.

GET/openapi.json

Esta API como documento OpenAPI 3.1

No necesita clave.

curl

curl "https://api.yellowecuador.com/api/public/v1/openapi.json"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/openapi.json", {
});
const data = await res.json();

Responde 200 cuando todo va bien.

Datos de referencia

Los códigos que usan las propiedades: provincias, cantones y parroquias (INEC), tipos y características.

GET/reference/provinces

Provincias (códigos INEC)

Cualquier clave válida.

curl

curl "https://api.yellowecuador.com/api/public/v1/reference/provinces" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/reference/provinces", {
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
  },
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 429.

GET/reference/cantons

Cantones, opcionalmente de una provincia

Cualquier clave válida.

Parámetros

  • provinceCode (en la consulta)string

curl

curl "https://api.yellowecuador.com/api/public/v1/reference/cantons" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/reference/cantons", {
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
  },
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 429.

GET/reference/parishes

Parroquias de un cantón

Cualquier clave válida.

Parámetros

  • cantonCode (en la consulta, obligatorio)string

curl

curl "https://api.yellowecuador.com/api/public/v1/reference/parishes" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/reference/parishes", {
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
  },
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 429.

GET/reference/property-types

Tipos de propiedad permitidos por categoría

Cualquier clave válida.

curl

curl "https://api.yellowecuador.com/api/public/v1/reference/property-types" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/reference/property-types", {
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
  },
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 429.

GET/reference/features

Códigos de características que admite una propiedad

Cualquier clave válida.

curl

curl "https://api.yellowecuador.com/api/public/v1/reference/features" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/reference/features", {
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
  },
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 429.

Propiedades

Las propiedades de la inmobiliaria, en cualquier estado. Crear, editar y publicar pasa por las mismas reglas y los mismos límites del plan que en el panel.

GET/listings

Las propiedades de la inmobiliaria, en cualquier estado, de la más nueva a la más antigua

Permiso necesario: listings:read

Parámetros

  • limit (en la consulta)integer
  • cursor (en la consulta)string (uuid)
  • updatedSince (en la consulta)string (date-time)
  • status (en la consulta)"DRAFT" | "ACTIVE" | "PAUSED" | "SOLD" | "RENTED" | "ARCHIVED"

curl

curl "https://api.yellowecuador.com/api/public/v1/listings" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/listings", {
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
  },
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 429.

GET/listings/{id}

Una propiedad

Permiso necesario: listings:read

Parámetros

  • id (en la ruta, obligatorio)string (uuid)

curl

curl "https://api.yellowecuador.com/api/public/v1/listings/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/listings/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f", {
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
  },
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 404, 429.

POST/listings

Crear una propiedad en borrador

Permiso necesario: listings:write

La propiedad se crea como borrador a nombre del propietario en cuyo nombre actúa la clave, o de `ownerId`. Se publica con otra llamada, con las mismas reglas y límites del plan que en el panel. Una inmobiliaria puede crear 600 propiedades por hora por la API (más si su contrato fija un límite mayor; pasado ese cupo, 429) y tener 500 borradores creados por la API (2 000 con importación masiva; pasado ese tope, 409 `too_many_drafts`). Ninguno de los dos consume el cupo propio del propietario en el panel.

Parámetros

  • Idempotency-Key (cabecera)string

Cuerpo (JSON)

  • operation (obligatorio)"SALE" | "RENT" | "SHORT_TERM"
  • category (obligatorio)"RESIDENTIAL" | "COMMERCIAL" | "LAND" | "NEW_CONSTRUCTION"
  • propertyType (obligatorio)"HOUSE" | "APARTMENT" | "SUITE" | "PENTHOUSE" | "TOWNHOUSE" | "FARM" | "OFFICE" | "RETAIL" | "WAREHOUSE" | "BUILDING" | "HOTEL" | "LAND"
  • title (obligatorio)string
  • descriptionstring | null
  • pricenumber | null
  • priceNegotiableboolean
  • hoaFeenumber | null
  • provinceCodestring | null
  • cantonCodestring | null
  • parishCodestring | null
  • addressLinestring | null
  • neighborhoodstring | null
  • latnumber | null
  • lngnumber | null
  • showExactLocationboolean
  • bedroomsinteger | null
  • bathroomsinteger | null
  • halfBathroomsinteger | null
  • parkingSpacesinteger | null
  • areaM2number | null
  • lotAreaM2number | null
  • floorsinteger | null
  • floorNumberinteger | null
  • yearBuiltinteger | null
  • furnishedboolean
  • petsAllowedboolean
  • featuresarray of "POOL" | "GYM" | "BBQ" | "GARDEN" | "TERRACE" | "BALCONY" | "ELEVATOR" | "SECURITY_24H" | "GATED_COMMUNITY" | "AIR_CONDITIONING" | "HEATING" | "FIREPLACE" | "LAUNDRY" | "STORAGE" | "FURNISHED" | "PET_FRIENDLY" | "SOLAR_PANELS" | "WATER_TANK" | "GENERATOR" | "PLAYGROUND" | "VIEW_CITY" | "VIEW_MOUNTAIN" | "VIEW_OCEAN" | "VIEW_RIVER" | "PARKING_VISITORS" | "DISABLED_ACCESS"
  • projectNamestring | null
  • deliveryDatestring (date) | null
  • videoUrlstring (uri) | null
  • virtualTourUrlstring (uri) | null
  • pricePeriod"NIGHT" | "WEEK" | "MONTH" | null
  • priceOnRequestboolean
  • seismicCondition"NOT_STATED" | "NO_VISIBLE_DAMAGE" | "INSPECTED" | "REPAIRED"
  • seismicYearinteger | null
  • ownerIdstring (uuid)

curl

curl -X POST "https://api.yellowecuador.com/api/public/v1/listings" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY" \
  -H "Idempotency-Key: 7c1f0d3a-example-key" \
  -H "Content-Type: application/json" \
  -d '{"operation":"SALE","category":"RESIDENTIAL","propertyType":"HOUSE","title":"Casa de tres dormitorios en Cumbayá","price":185000,"provinceCode":"17","cantonCode":"1701"}'

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/listings", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({"operation":"SALE","category":"RESIDENTIAL","propertyType":"HOUSE","title":"Casa de tres dormitorios en Cumbayá","price":185000,"provinceCode":"17","cantonCode":"1701"}),
});
const data = await res.json();

Responde 201 cuando todo va bien. Errores posibles: 400, 401, 403, 409, 422, 429.

PATCH/listings/{id}

Cambiar el contenido de una propiedad

Permiso necesario: listings:write

Parámetros

  • id (en la ruta, obligatorio)string (uuid)

Cuerpo (JSON)

  • operation"SALE" | "RENT" | "SHORT_TERM"
  • category"RESIDENTIAL" | "COMMERCIAL" | "LAND" | "NEW_CONSTRUCTION"
  • propertyType"HOUSE" | "APARTMENT" | "SUITE" | "PENTHOUSE" | "TOWNHOUSE" | "FARM" | "OFFICE" | "RETAIL" | "WAREHOUSE" | "BUILDING" | "HOTEL" | "LAND"
  • titlestring
  • descriptionstring | null
  • pricenumber | null
  • priceNegotiableboolean
  • hoaFeenumber | null
  • provinceCodestring | null
  • cantonCodestring | null
  • parishCodestring | null
  • addressLinestring | null
  • neighborhoodstring | null
  • latnumber | null
  • lngnumber | null
  • showExactLocationboolean
  • bedroomsinteger | null
  • bathroomsinteger | null
  • halfBathroomsinteger | null
  • parkingSpacesinteger | null
  • areaM2number | null
  • lotAreaM2number | null
  • floorsinteger | null
  • floorNumberinteger | null
  • yearBuiltinteger | null
  • furnishedboolean
  • petsAllowedboolean
  • featuresarray of "POOL" | "GYM" | "BBQ" | "GARDEN" | "TERRACE" | "BALCONY" | "ELEVATOR" | "SECURITY_24H" | "GATED_COMMUNITY" | "AIR_CONDITIONING" | "HEATING" | "FIREPLACE" | "LAUNDRY" | "STORAGE" | "FURNISHED" | "PET_FRIENDLY" | "SOLAR_PANELS" | "WATER_TANK" | "GENERATOR" | "PLAYGROUND" | "VIEW_CITY" | "VIEW_MOUNTAIN" | "VIEW_OCEAN" | "VIEW_RIVER" | "PARKING_VISITORS" | "DISABLED_ACCESS"
  • projectNamestring | null
  • deliveryDatestring (date) | null
  • videoUrlstring (uri) | null
  • virtualTourUrlstring (uri) | null
  • pricePeriod"NIGHT" | "WEEK" | "MONTH" | null
  • priceOnRequestboolean
  • seismicCondition"NOT_STATED" | "NO_VISIBLE_DAMAGE" | "INSPECTED" | "REPAIRED"
  • seismicYearinteger | null

curl

curl -X PATCH "https://api.yellowecuador.com/api/public/v1/listings/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"price":179000,"description":"Casa luminosa con jardín, a cinco minutos del parque."}'

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/listings/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f", {
  method: "PATCH",
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"price":179000,"description":"Casa luminosa con jardín, a cinco minutos del parque."}),
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 404, 422, 429.

POST/listings/{id}/publish

Publicar un borrador

Permiso necesario: listings:write

Parámetros

  • id (en la ruta, obligatorio)string (uuid)
  • Idempotency-Key (cabecera)string

curl

curl -X POST "https://api.yellowecuador.com/api/public/v1/listings/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f/publish" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY" \
  -H "Idempotency-Key: 7c1f0d3a-example-key"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/listings/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f/publish", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
    "Idempotency-Key": crypto.randomUUID(),
  },
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 402, 403, 404, 409, 422, 429.

POST/listings/{id}/status

Pausar, reanudar, marcar como vendida o alquilada, archivar

Permiso necesario: listings:write

Parámetros

  • id (en la ruta, obligatorio)string (uuid)
  • Idempotency-Key (cabecera)string

Cuerpo (JSON)

  • status (obligatorio)"ACTIVE" | "PAUSED" | "SOLD" | "RENTED" | "ARCHIVED" | "DRAFT"

curl

curl -X POST "https://api.yellowecuador.com/api/public/v1/listings/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f/status" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY" \
  -H "Idempotency-Key: 7c1f0d3a-example-key" \
  -H "Content-Type: application/json" \
  -d '{"status":"PAUSED"}'

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/listings/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f/status", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({"status":"PAUSED"}),
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 402, 403, 404, 409, 422, 429.

POST/listings/{id}/media

Añadir una foto desde una URL

Permiso necesario: listings:write

La imagen se descarga por https desde internet público (JPEG, PNG o WebP, hasta 10 MB) y se procesa como una subida: se eliminan los metadatos y se generan los tamaños. `status` es PROCESSING hasta entonces.

Parámetros

  • id (en la ruta, obligatorio)string (uuid)
  • Idempotency-Key (cabecera)string

Cuerpo (JSON)

  • url (obligatorio)string
  • captionstring | null

curl

curl -X POST "https://api.yellowecuador.com/api/public/v1/listings/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f/media" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY" \
  -H "Idempotency-Key: 7c1f0d3a-example-key" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://images.example.com/casa/fachada.jpg","caption":"Fachada"}'

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/listings/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f/media", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({"url":"https://images.example.com/casa/fachada.jpg","caption":"Fachada"}),
});
const data = await res.json();

Responde 201 cuando todo va bien. Errores posibles: 400, 401, 403, 404, 422, 429, 503.

Contactos

Los contactos del CRM de la inmobiliaria. Un contacto enviado por la API entra como cualquier otro: se suma al contacto abierto que ya tiene ese correo o teléfono, o se crea y se asigna según las reglas de la inmobiliaria. La respuesta dice su id y si es nuevo —así que “created”: false le dice a quien llama que existe un contacto con ese correo o teléfono—. El contacto completo solo vuelve a una clave que también puede leer contactos; un cambio de etapa responde solo el id, la etapa y la versión a una clave que no puede.

GET/leads

Los contactos de la inmobiliaria, del más nuevo al más antiguo

Permiso necesario: leads:read

Parámetros

  • limit (en la consulta)integer
  • cursor (en la consulta)string (uuid)
  • updatedSince (en la consulta)string (date-time)
  • stage (en la consulta)"NEW" | "CONTACTED" | "QUALIFIED" | "APPOINTMENT" | "SHOWING" | "OFFER" | "NEGOTIATION" | "WON" | "LOST"

curl

curl "https://api.yellowecuador.com/api/public/v1/leads" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/leads", {
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
  },
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 429.

GET/leads/{id}

Un contacto

Permiso necesario: leads:read

Parámetros

  • id (en la ruta, obligatorio)string (uuid)

curl

curl "https://api.yellowecuador.com/api/public/v1/leads/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/leads/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f", {
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
  },
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 404, 429.

POST/leads

Enviar un contacto

Permiso necesario: leads:write

El contacto entra al CRM como cualquier contacto entrante: si la inmobiliaria ya tiene uno abierto con ese correo o teléfono, esta solicitud se añade a él (`created: false`); si no, se crea y se asigna según las reglas de la inmobiliaria. La respuesta es `{ id, created }`; el contacto completo viene en `lead` solo si la clave tiene también `leads:read`. Ten en cuenta que `created: false` le dice a quien llama que la inmobiliaria ya tiene un contacto abierto con ese correo o teléfono: es la regla del CRM para toda fuente entrante.

Parámetros

  • Idempotency-Key (cabecera)string

Cuerpo (JSON)

  • name (obligatorio)string
  • emailstring (email)
  • phonestring
  • listingIdstring (uuid)
  • messagestring
  • ownerIdstring (uuid)
  • locale"es-EC" | "en"

curl

curl -X POST "https://api.yellowecuador.com/api/public/v1/leads" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY" \
  -H "Idempotency-Key: 7c1f0d3a-example-key" \
  -H "Content-Type: application/json" \
  -d '{"name":"María Andrade","email":"maria@example.com","phone":"+593 99 123 4567","message":"Quisiera visitar la casa el sábado."}'

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/leads", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({"name":"María Andrade","email":"maria@example.com","phone":"+593 99 123 4567","message":"Quisiera visitar la casa el sábado."}),
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 404, 409, 422, 429.

POST/leads/{id}/stage

Mover un contacto a otra etapa

Permiso necesario: leads:write

La respuesta es el contacto completo si la clave tiene también `leads:read`; si no, `{ id, stage, version }`.

Parámetros

  • id (en la ruta, obligatorio)string (uuid)
  • Idempotency-Key (cabecera)string

Cuerpo (JSON)

  • stage (obligatorio)"NEW" | "CONTACTED" | "QUALIFIED" | "APPOINTMENT" | "SHOWING" | "OFFER" | "NEGOTIATION" | "WON" | "LOST"
  • lostReason"PRICE" | "FINANCING" | "CHOSE_OTHER" | "NOT_RESPONSIVE" | "NOT_QUALIFIED" | "TIMING" | "OTHER" | null
  • lostNotestring | null
  • dealobject
  • versioninteger

curl

curl -X POST "https://api.yellowecuador.com/api/public/v1/leads/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f/stage" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY" \
  -H "Idempotency-Key: 7c1f0d3a-example-key" \
  -H "Content-Type: application/json" \
  -d '{"stage":"CONTACTED"}'

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/leads/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f/stage", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({"stage":"CONTACTED"}),
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 404, 409, 429.

POST/leads/{id}/notes

Añadir una nota a un contacto

Permiso necesario: leads:write

Las notas las ve el equipo en el panel. La API nunca devuelve notas.

Parámetros

  • id (en la ruta, obligatorio)string (uuid)
  • Idempotency-Key (cabecera)string

Cuerpo (JSON)

  • body (obligatorio)string

curl

curl -X POST "https://api.yellowecuador.com/api/public/v1/leads/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f/notes" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY" \
  -H "Idempotency-Key: 7c1f0d3a-example-key" \
  -H "Content-Type: application/json" \
  -d '{"body":"Llamó por la tarde; prefiere visitas los sábados."}'

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/leads/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f/notes", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({"body":"Llamó por la tarde; prefiere visitas los sábados."}),
});
const data = await res.json();

Responde 201 cuando todo va bien. Errores posibles: 400, 401, 402, 403, 404, 429.

Citas

Las citas de la inmobiliaria. Solo lectura.

GET/appointments

Las citas de la inmobiliaria, de la más nueva a la más antigua

Permiso necesario: appointments:read

Parámetros

  • limit (en la consulta)integer
  • cursor (en la consulta)string (uuid)
  • updatedSince (en la consulta)string (date-time)
  • status (en la consulta)"REQUESTED" | "CONFIRMED" | "DECLINED" | "CANCELLED"
  • from (en la consulta)string (date-time)

curl

curl "https://api.yellowecuador.com/api/public/v1/appointments" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/appointments", {
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
  },
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 429.

GET/appointments/{id}

Una cita

Permiso necesario: appointments:read

Parámetros

  • id (en la ruta, obligatorio)string (uuid)

curl

curl "https://api.yellowecuador.com/api/public/v1/appointments/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/appointments/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f", {
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
  },
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 404, 429.

Endpoints de webhook

Administra desde tu código los mismos endpoints que ves en el panel.

GET/webhook-endpoints

Endpoints de webhook

Permiso necesario: webhooks:manage

Solo se listan los endpoints que creó esta clave (los de otra clave o los del panel no los administra ella); las URL se muestran enmascaradas.

Parámetros

  • limit (en la consulta)integer
  • cursor (en la consulta)string (uuid)

curl

curl "https://api.yellowecuador.com/api/public/v1/webhook-endpoints" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/webhook-endpoints", {
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
  },
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 429.

POST/webhook-endpoints

Crear un endpoint de webhook

Permiso necesario: webhooks:manage

El secreto de firma está en esta respuesta y en ningún otro sitio: un reintento con la misma Idempotency-Key responde el mismo endpoint sin él (`secretShown: false`). Recibir un evento es leer su recurso, así que la clave necesita el permiso de lectura de cada familia de eventos: `leads:read` para `lead.*` y `whatsapp.click`, `appointments:read` para `appointment.*`, `listings:read` para `listing.*` y `review.*`.

Parámetros

  • Idempotency-Key (cabecera)string

Cuerpo (JSON)

  • url (obligatorio)string
  • descriptionstring
  • events (obligatorio)array of "lead.created" | "lead.updated" | "lead.stage_changed" | "lead.assigned" | "lead.deleted" | "listing.published" | "listing.updated" | "listing.status_changed" | "listing.deleted" | "appointment.created" | "appointment.status_changed" | "review.published" | "whatsapp.click"
  • kind"GENERIC" | "ZAPIER" | "MAKE" | "N8N"

curl

curl -X POST "https://api.yellowecuador.com/api/public/v1/webhook-endpoints" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY" \
  -H "Idempotency-Key: 7c1f0d3a-example-key" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/leads","events":["lead.created","lead.stage_changed"]}'

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/webhook-endpoints", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({"url":"https://example.com/hooks/leads","events":["lead.created","lead.stage_changed"]}),
});
const data = await res.json();

Responde 201 cuando todo va bien. Errores posibles: 400, 401, 402, 403, 409, 422, 429, 503.

GET/webhook-endpoints/{id}

Un endpoint de webhook

Permiso necesario: webhooks:manage

Parámetros

  • id (en la ruta, obligatorio)string (uuid)

curl

curl "https://api.yellowecuador.com/api/public/v1/webhook-endpoints/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/webhook-endpoints/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f", {
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
  },
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 403, 404, 429.

PATCH/webhook-endpoints/{id}

Cambiar, pausar o reanudar un endpoint de webhook

Permiso necesario: webhooks:manage

Parámetros

  • id (en la ruta, obligatorio)string (uuid)

Cuerpo (JSON)

  • urlstring
  • descriptionstring | null
  • eventsarray of "lead.created" | "lead.updated" | "lead.stage_changed" | "lead.assigned" | "lead.deleted" | "listing.published" | "listing.updated" | "listing.status_changed" | "listing.deleted" | "appointment.created" | "appointment.status_changed" | "review.published" | "whatsapp.click"
  • status"ACTIVE" | "PAUSED"

curl

curl -X PATCH "https://api.yellowecuador.com/api/public/v1/webhook-endpoints/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"PAUSED"}'

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/webhook-endpoints/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f", {
  method: "PATCH",
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"status":"PAUSED"}),
});
const data = await res.json();

Responde 200 cuando todo va bien. Errores posibles: 400, 401, 402, 403, 404, 422, 429, 503.

DELETE/webhook-endpoints/{id}

Eliminar un endpoint de webhook y su registro de entregas

Permiso necesario: webhooks:manage

Parámetros

  • id (en la ruta, obligatorio)string (uuid)

curl

curl -X DELETE "https://api.yellowecuador.com/api/public/v1/webhook-endpoints/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f" \
  -H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"

JavaScript

const res = await fetch("https://api.yellowecuador.com/api/public/v1/webhook-endpoints/3f2c9a1e-5b7d-4c8a-9e21-0a1b2c3d4e5f", {
  method: "DELETE",
  headers: {
    Authorization: "Bearer " + process.env.API_KEY,
  },
});
if (!res.ok) throw new Error(await res.text());

Responde 204 cuando todo va bien. Errores posibles: 400, 401, 403, 404, 429.

Webhooks

Un webhook es un POST que hacemos a una URL tuya cuando algo cambia. Los endpoints se crean en el panel o con la API, y cada uno elige qué eventos recibe. Recibir un evento es leer su recurso: una clave suscribe un endpoint solo a los eventos que puede leer (leads:read para contactos y clics de WhatsApp, appointments:read para citas, listings:read para propiedades y reseñas), y administra solo los endpoints que creó por la API.

Eventos

  • lead.createdEntró un contacto nuevo
  • lead.updatedCambiaron los datos de un contacto
  • lead.stage_changedUn contacto cambió de etapa
  • lead.assignedUn contacto pasó a otro agente
  • lead.deletedSe eliminó un contacto
  • listing.publishedSe publicó una propiedad
  • listing.updatedCambió el contenido de una propiedad que no es borrador
  • listing.status_changedUna propiedad cambió de estado
  • listing.deletedSe eliminó una propiedad
  • appointment.createdSe pidió o agendó una cita
  • appointment.status_changedUna cita cambió de estado
  • review.publishedSe publicó una reseña
  • whatsapp.clickAlguien abrió WhatsApp desde una propiedad o una página de la inmobiliaria o de sus agentes

El aviso

“data” es el recurso tal como lo devuelve la API cuando se envía el mensaje: se lee del registro en ese momento, nunca de una copia guardada, así que un reintento o un reenvío lleva el estado actual. En los cambios, “previous” trae el valor anterior de la etapa, el estado o el agente. Un recurso eliminado entretanto llega como su id con “deleted”: true; un evento *.deleted, como su id.

{
  "id": "5b0c7a52-6f4e-4d59-9a0e-2f1f0d6c9e11",
  "type": "lead.created",
  "createdAt": "2026-10-03T15:04:05.000Z",
  "apiVersion": "v1",
  "brokerageId": "…",
  "data": {
    "id": "…",
    "name": "María Andrade",
    "email": "maria@example.com",
    "phone": "+593 99 123 4567",
    "stage": "NEW",
    "source": "INQUIRY",
    "owner": {
      "id": "…",
      "name": "Carlos Vera"
    },
    "listingIds": [
      "…"
    ]
  }
}

Firma

Cada aviso lleva la cabecera X-TYP-Signature: el momento del envío (t, en segundos) y una o más firmas v1, cada una el HMAC-SHA256 en hexadecimal de «t.cuerpo» con el secreto del endpoint. Comprueba la firma sobre el cuerpo exacto que recibiste, compara en tiempo constante y rechaza los avisos cuyo t se aleje más de 300 segundos de tu reloj. Tras rotar el secreto llegan dos firmas durante 24 horas: la del secreto nuevo y la del anterior.

X-TYP-Signature: t=1790000000,v1=<64 hex>
import { createHmac, timingSafeEqual } from "node:crypto";

// `rawBody` is the exact bytes received — verify before parsing the JSON.
export function isAuthentic(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = String(header).split(",").map((p) => p.trim().split("="));
  const t = Number(parts.find(([k]) => k === "t")?.[1]);
  const signatures = parts.filter(([k]) => k === "v1").map(([, v]) => v);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  return signatures.some((s) => {
    const given = Buffer.from(s, "hex");
    return given.length === expected.length && timingSafeEqual(given, expected);
  });
}

// isAuthentic(body, req.headers["x-typ-signature"], process.env.WEBHOOK_SECRET)

Entrega

  • Solo enviamos a URLs https de internet público, por el puerto 443. No seguimos redirecciones.
  • Tu servidor tiene 5 segundos para responder con un código 2xx. Cualquier otra respuesta cuenta como fallo.
  • Un fallo se reintenta hasta completar 8 intentos en unas 24 horas (tras 1 min, 5 min, 30 min, 2 h, 5 h, 8 h y 8 h).
  • Si un endpoint sigue fallando durante más de 24 horas (20 intentos fallidos, contados como mucho uno por minuto; un problema de nuestro lado nunca cuenta) lo desactivamos y avisamos por correo a los dueños. Mientras esté desactivado no se guardan eventos para él; cuando vuelva a estar activo, «Reenviar todo lo fallido» reenvía las fallas de los últimos 7 días.
  • Cuando hay 1000 mensajes esperando para un endpoint, los nuevos se registran como fallidos en vez de encolarse, y se avisa a los dueños una vez al día.
  • Los avisos pueden llegar repetidos o desordenados. Usa el id del evento para no procesar uno dos veces.
  • El panel guarda el registro de entregas de los últimos 30 días y permite reenviar cualquiera.