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": "…"
}- 401
invalid_api_keyFalta la clave, está mal formada, revocada o vencida, o la inmobiliaria ya no tiene acceso a la API. - 403
insufficient_scopeLa clave no tiene el permiso que esta ruta necesita (ver `scope` en la respuesta). - 403
no_acting_ownerLa inmobiliaria no tiene un propietario activo en cuyo nombre actuar. - 404
not_foundEse recurso no existe en esta inmobiliaria. - 400
validationEl cuerpo o los parámetros no son válidos; `errors` enumera cada campo. - 400
invalid_cursorEl cursor no pertenece a esta lista. - 429
rate_limitedDemasiadas solicitudes (por inmobiliaria y por clave, o el cupo por hora de propiedades creadas); espere los segundos de `Retry-After`. - 409
too_many_draftsLa inmobiliaria tiene 500 borradores creados por la API (2 000 con importación masiva); publique o elimine algunos primero. - 402
plan_requiredEl plan no incluye esta función. - 402
plan_limit_reachedSe alcanzó un límite del plan (propiedades activas). - 409
invalid_transitionLa propiedad no puede pasar de su estado al solicitado. - 409
version_conflictEl contacto cambió desde que se leyó `version`. - 409
duplicate_contactReabrir el contacto duplicaría uno abierto. - 409
idempotency_in_progressUna solicitud con esta Idempotency-Key sigue en curso. - 422
idempotency_key_reusedEsta Idempotency-Key se usó con otra solicitud. - 422
listing_incompleteA la propiedad le falta lo que exige la publicación; `errors` lo enumera. - 422
unknown_geoProvincia, cantón o parroquia desconocidos o incoherentes. - 422
not_a_member`ownerId` no es un miembro profesional activo de la inmobiliaria. - 422
unsafe_urlLa URL no es una dirección https de internet público. - 422
fetch_failedNo se pudo descargar la imagen. - 422
not_an_imageEl archivo no es una imagen JPEG, PNG o WebP. - 422
too_largeLa imagen supera los 10 MB. - 422
media_limitUna propiedad admite 30 imágenes como máximo. - 503
outbound_busyNuestra capacidad de salida está llena por un momento; reintenta tras los segundos de `Retry-After`. - 409
endpoint_limitUna inmobiliaria admite 10 endpoints de webhook como máximo.
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.
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.
Parámetros
limit (en la consulta)integercursor (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.
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)stringdescriptionstring | nullpricenumber | nullpriceNegotiablebooleanhoaFeenumber | nullprovinceCodestring | nullcantonCodestring | nullparishCodestring | nulladdressLinestring | nullneighborhoodstring | nulllatnumber | nulllngnumber | nullshowExactLocationbooleanbedroomsinteger | nullbathroomsinteger | nullhalfBathroomsinteger | nullparkingSpacesinteger | nullareaM2number | nulllotAreaM2number | nullfloorsinteger | nullfloorNumberinteger | nullyearBuiltinteger | nullfurnishedbooleanpetsAllowedbooleanfeaturesarray 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 | nulldeliveryDatestring (date) | nullvideoUrlstring (uri) | nullvirtualTourUrlstring (uri) | nullpricePeriod"NIGHT" | "WEEK" | "MONTH" | nullpriceOnRequestbooleanseismicCondition"NOT_STATED" | "NO_VISIBLE_DAMAGE" | "INSPECTED" | "REPAIRED"seismicYearinteger | nullownerIdstring (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.
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"titlestringdescriptionstring | nullpricenumber | nullpriceNegotiablebooleanhoaFeenumber | nullprovinceCodestring | nullcantonCodestring | nullparishCodestring | nulladdressLinestring | nullneighborhoodstring | nulllatnumber | nulllngnumber | nullshowExactLocationbooleanbedroomsinteger | nullbathroomsinteger | nullhalfBathroomsinteger | nullparkingSpacesinteger | nullareaM2number | nulllotAreaM2number | nullfloorsinteger | nullfloorNumberinteger | nullyearBuiltinteger | nullfurnishedbooleanpetsAllowedbooleanfeaturesarray 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 | nulldeliveryDatestring (date) | nullvideoUrlstring (uri) | nullvirtualTourUrlstring (uri) | nullpricePeriod"NIGHT" | "WEEK" | "MONTH" | nullpriceOnRequestbooleanseismicCondition"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.
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.
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)stringcaptionstring | 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.
Parámetros
limit (en la consulta)integercursor (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.
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)stringemailstring (email)phonestringlistingIdstring (uuid)messagestringownerIdstring (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" | nulllostNotestring | nulldealobjectversioninteger
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.
Parámetros
id (en la ruta, obligatorio)string (uuid)Idempotency-Key (cabecera)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.
Parámetros
limit (en la consulta)integercursor (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.
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)integercursor (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)stringdescriptionstringevents (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.
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.
Parámetros
id (en la ruta, obligatorio)string (uuid)
Cuerpo (JSON)
urlstringdescriptionstring | nulleventsarray 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.
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 nuevolead.updatedCambiaron los datos de un contactolead.stage_changedUn contacto cambió de etapalead.assignedUn contacto pasó a otro agentelead.deletedSe eliminó un contactolisting.publishedSe publicó una propiedadlisting.updatedCambió el contenido de una propiedad que no es borradorlisting.status_changedUna propiedad cambió de estadolisting.deletedSe eliminó una propiedadappointment.createdSe pidió o agendó una citaappointment.status_changedUna cita cambió de estadoreview.publishedSe publicó una reseñawhatsapp.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.