Authentication
Every request carries a key in the Authorization header, with the Bearer scheme. The key belongs to one brokerage: everything it reads or writes is that brokerage’s.
curl "https://api.yellowecuador.com/api/public/v1/me" \
-H "Authorization: Bearer typ_live_EXAMPLE_REPLACE_WITH_YOUR_KEY"
- The key is shown once, when it is created. We keep only its fingerprint.
- Use it from a server only. Do not put it in a web page or an app: whoever sees it can use it.
- A revoked or expired key stops working on the next request.
Scopes
listings:readRead the listings.listings:writeCreate, edit and publish listings; add photos.leads:readRead the leads.leads:writeSend leads in, move their stage and add notes.appointments:readRead the appointments.webhooks:manageCreate, change and delete webhook endpoints.
Conventions
- Everything is JSON, in UTF-8. Dates are ISO 8601 instants in UTC.
- An id that is not your brokerage’s answers 404, exactly like one that does not exist.
- Lists return “data” and “nextCursor”. Ask for the next page with cursor=nextCursor; when it is null there is no more. The limit parameter goes from 1 to 100 (25 when you send none).
- Your brokerage may make 120 requests a minute in all — every key counts toward the same number, and one key may use at most half of it (at least 30) so it cannot starve the others — unless your contract says another number. The RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers tell you how many are left; past the limit the answer is 429 with Retry-After.
- Listings created through the API have their own budget, apart from the owner's in the dashboard: 600 an hour per brokerage (more if your contract raises the rate) and at most 500 drafts made through the API (2000 with bulk import). Past them the answer is 429, or 409 too_many_drafts.
- Any POST may carry the Idempotency-Key header. If you repeat the request with the same key and the same body within 24 hours, you get the original answer and nothing is created twice.
- An unknown field in the body or the query is a 400 error: nothing is ignored in silence.
- The version is in the path. Inside v1 we only add: new fields, new routes, new events.
Errors
An error arrives as application/problem+json. The last segment of “type” is the code in the table; “detail” explains it in English and, for validation errors, “errors” lists every field.
{
"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_keyThe key is missing, malformed, revoked, expired, or its brokerage no longer has API access. - 403
insufficient_scopeThe key does not hold the scope this route needs (see `scope` in the body). - 403
no_acting_ownerThe brokerage has no active owner the key could act for. - 404
not_foundNo such resource in this brokerage. - 400
validationThe body or the query is not valid; `errors` lists every field. - 400
invalid_cursorThe cursor does not belong to this list. - 429
rate_limitedToo many requests (per brokerage and per key, or the hourly budget of listing creations); wait `Retry-After` seconds. - 409
too_many_draftsThe brokerage holds 500 drafts made through the API (2 000 with bulk import); publish or delete some first. - 402
plan_requiredThe plan does not include this feature. - 402
plan_limit_reachedA limit of the plan was reached (active listings). - 409
invalid_transitionThe listing cannot go from its status to the one asked. - 409
version_conflictThe lead changed since `version` was read. - 409
duplicate_contactReopening the lead would duplicate an open one. - 409
idempotency_in_progressA request with this Idempotency-Key is still running. - 422
idempotency_key_reusedThis Idempotency-Key was used with a different request. - 422
listing_incompleteThe listing lacks what publishing requires; `errors` lists it. - 422
unknown_geoUnknown or inconsistent province / canton / parish. - 422
not_a_member`ownerId` is not an active professional member of the brokerage. - 422
unsafe_urlThe URL is not an https address on the public internet. - 422
fetch_failedThe image could not be downloaded. - 422
not_an_imageThe file is not a JPEG, PNG or WebP image. - 422
too_largeThe image is larger than 10 MB. - 422
media_limitA listing holds at most 30 images. - 503
outbound_busyOur outbound capacity is momentarily full; retry after `Retry-After` seconds. - 409
endpoint_limitA brokerage holds at most 10 webhook endpoints.
Check a key, and download this same reference as an OpenAPI document.
GET/me
The key in use, its scopes and its rate limit
Any valid key.
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();Answers 200 when all is well. Possible errors: 400, 401, 403, 429.
GET/openapi.json
This API as an OpenAPI 3.1 document
Needs no key.
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();Answers 200 when all is well.
Reference data
The codes listings use: provinces, cantons and parishes (INEC), property types and features.
GET/reference/provinces
Provinces (INEC codes)
Any valid key.
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();Answers 200 when all is well. Possible errors: 400, 401, 403, 429.
GET/reference/cantons
Cantons, optionally of one province
Any valid key.
Parameters
provinceCode (in the query)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();Answers 200 when all is well. Possible errors: 400, 401, 403, 429.
GET/reference/parishes
Parishes of one canton
Any valid key.
Parameters
cantonCode (in the query, required)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();Answers 200 when all is well. Possible errors: 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();Answers 200 when all is well. Possible errors: 400, 401, 403, 429.
GET/reference/features
Feature codes a listing may carry
Any valid key.
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();Answers 200 when all is well. Possible errors: 400, 401, 403, 429.
Listings
The brokerage’s listings, in any status. Creating, editing and publishing go through the same rules and the same plan limits as the dashboard.
Parameters
limit (in the query)integercursor (in the query)string (uuid)updatedSince (in the query)string (date-time)status (in the query)"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();Answers 200 when all is well. Possible errors: 400, 401, 403, 429.
Parameters
id (in the path, required)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();Answers 200 when all is well. Possible errors: 400, 401, 403, 404, 429.
POST/listings
Create a draft listing
Scope needed: listings:write
The listing is created as a draft owned by the key's acting owner, or by `ownerId`. It is published with a separate call, under the same rules and plan limits as in the dashboard. A brokerage may create 600 listings an hour through the API (more with a higher rate agreed in its contract; past it, 429) and hold 500 drafts made through the API (2 000 with bulk import; past it, 409 `too_many_drafts`). Neither uses the owner's own budget in the dashboard.
Parameters
Idempotency-Key (header)string
Body (JSON)
operation (required)"SALE" | "RENT" | "SHORT_TERM"category (required)"RESIDENTIAL" | "COMMERCIAL" | "LAND" | "NEW_CONSTRUCTION"propertyType (required)"HOUSE" | "APARTMENT" | "SUITE" | "PENTHOUSE" | "TOWNHOUSE" | "FARM" | "OFFICE" | "RETAIL" | "WAREHOUSE" | "BUILDING" | "HOTEL" | "LAND"title (required)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();Answers 201 when all is well. Possible errors: 400, 401, 403, 409, 422, 429.
Parameters
id (in the path, required)string (uuid)
Body (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();Answers 200 when all is well. Possible errors: 400, 401, 403, 404, 422, 429.
Parameters
id (in the path, required)string (uuid)Idempotency-Key (header)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();Answers 200 when all is well. Possible errors: 400, 401, 402, 403, 404, 409, 422, 429.
Parameters
id (in the path, required)string (uuid)Idempotency-Key (header)string
Body (JSON)
status (required)"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();Answers 200 when all is well. Possible errors: 400, 401, 402, 403, 404, 409, 422, 429.
POST/listings/{id}/media
Add a photo from a URL
Scope needed: listings:write
The image is downloaded over https from the public internet (JPEG, PNG or WebP, up to 10 MB), then processed like an upload: metadata is removed and the sizes are generated. `status` is PROCESSING until that is done.
Parameters
id (in the path, required)string (uuid)Idempotency-Key (header)string
Body (JSON)
url (required)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();Answers 201 when all is well. Possible errors: 400, 401, 403, 404, 422, 429, 503.
Leads
The leads in the brokerage’s CRM. A lead sent through the API comes in like any other: it joins the open lead that already has that e-mail or phone, or it is created and assigned by the brokerage’s own rules. The answer says its id and whether it was new — so “created”: false tells the caller that a contact with that e-mail or phone exists. The whole lead comes back only to a key that may also read leads; a stage move answers only the id, the stage and the version to a key that cannot.
GET/leads
The brokerage's leads, newest first
Scope needed: leads:read
Parameters
limit (in the query)integercursor (in the query)string (uuid)updatedSince (in the query)string (date-time)stage (in the query)"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();Answers 200 when all is well. Possible errors: 400, 401, 403, 429.
GET/leads/{id}
One lead
Scope needed: leads:read
Parameters
id (in the path, required)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();Answers 200 when all is well. Possible errors: 400, 401, 403, 404, 429.
POST/leads
Send a lead in
Scope needed: leads:write
The contact enters the CRM as any inbound lead does: if the brokerage already has an open lead with that e-mail or phone, this request is added to it (`created: false`); otherwise a lead is created and routed by the brokerage's rules. The answer is `{ id, created }`; the whole lead is in `lead` only when the key also holds `leads:read`. Note that `created: false` tells the caller that the brokerage already has an open lead with that e-mail or phone — the CRM's rule for every inbound source.
Parameters
Idempotency-Key (header)string
Body (JSON)
name (required)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();Answers 200 when all is well. Possible errors: 400, 401, 403, 404, 409, 422, 429.
POST/leads/{id}/stage
Move a lead to another stage
Scope needed: leads:write
The answer is the whole lead when the key also holds `leads:read`; otherwise `{ id, stage, version }`.
Parameters
id (in the path, required)string (uuid)Idempotency-Key (header)string
Body (JSON)
stage (required)"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();Answers 200 when all is well. Possible errors: 400, 401, 403, 404, 409, 429.
POST/leads/{id}/notes
Add a note to a lead
Scope needed: leads:write
Notes are visible to the team in the dashboard. The API never returns notes.
Parameters
id (in the path, required)string (uuid)Idempotency-Key (header)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();Answers 201 when all is well. Possible errors: 400, 401, 402, 403, 404, 429.
Appointments
The brokerage’s appointments. Read only.
Parameters
limit (in the query)integercursor (in the query)string (uuid)updatedSince (in the query)string (date-time)status (in the query)"REQUESTED" | "CONFIRMED" | "DECLINED" | "CANCELLED"from (in the query)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();Answers 200 when all is well. Possible errors: 400, 401, 403, 429.
Parameters
id (in the path, required)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();Answers 200 when all is well. Possible errors: 400, 401, 403, 404, 429.
Webhook endpoints
Manage from your code the same endpoints you see in the dashboard.
GET/webhook-endpoints
Webhook endpoints
Scope needed: webhooks:manage
Only the endpoints this key created are listed (another key's, or one made in the dashboard, is not this key's to manage); URLs are shown masked.
Parameters
limit (in the query)integercursor (in the query)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();Answers 200 when all is well. Possible errors: 400, 401, 403, 429.
POST/webhook-endpoints
Create a webhook endpoint
Scope needed: webhooks:manage
The signing secret is in this response and nowhere else: a retry with the same Idempotency-Key answers the same endpoint without it (`secretShown: false`). Receiving an event is reading its resource, so the key needs the read scope of every event family it subscribes to: `leads:read` for `lead.*` and `whatsapp.click`, `appointments:read` for `appointment.*`, `listings:read` for `listing.*` and `review.*`.
Parameters
Idempotency-Key (header)string
Body (JSON)
url (required)stringdescriptionstringevents (required)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();Answers 201 when all is well. Possible errors: 400, 401, 402, 403, 409, 422, 429, 503.
Parameters
id (in the path, required)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();Answers 200 when all is well. Possible errors: 400, 401, 403, 404, 429.
Parameters
id (in the path, required)string (uuid)
Body (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();Answers 200 when all is well. Possible errors: 400, 401, 402, 403, 404, 422, 429, 503.
Parameters
id (in the path, required)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());Answers 204 when all is well. Possible errors: 400, 401, 403, 404, 429.
Webhooks
A webhook is a POST we make to a URL of yours when something changes. Endpoints are created in the dashboard or through the API, and each one chooses which events it receives. Receiving an event is reading its resource: a key subscribes an endpoint only to events it may read (leads:read for lead events and WhatsApp clicks, appointments:read for appointments, listings:read for listings and reviews), and manages only the endpoints it created through the API.
Events
lead.createdA new lead came inlead.updatedA lead’s details changedlead.stage_changedA lead moved to another stagelead.assignedA lead went to another agentlead.deletedA lead was deletedlisting.publishedA listing was publishedlisting.updatedThe content of a listing that is not a draft changedlisting.status_changedA listing changed statuslisting.deletedA listing was deletedappointment.createdAn appointment was requested or bookedappointment.status_changedAn appointment changed statusreview.publishedA review was publishedwhatsapp.clickSomeone opened WhatsApp from a listing or a page of the brokerage or its agents
The message
“data” is the resource as the API returns it when the message is sent: it is read from the record at that moment, never from a stored copy, so a retry or a resend carries the current state. On changes, “previous” carries the old value of the stage, the status or the agent. A resource deleted in the meantime arrives as its id with “deleted”: true; a *.deleted event, as its 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": [
"…"
]
}
}Signature
Every message carries the X-TYP-Signature header: the send time (t, in seconds) and one or more v1 signatures, each the hexadecimal HMAC-SHA256 of “t.body” with the endpoint’s secret. Check the signature over the exact body you received, compare in constant time, and refuse messages whose t is more than 300 seconds from your clock. After a secret rotation two signatures arrive for 24 hours: the new secret’s and the previous one’s.
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)Delivery
- We only send to https URLs on the public internet, on port 443. We do not follow redirects.
- Your server has 5 seconds to answer with a 2xx code. Any other answer counts as a failure.
- A failure is retried until 8 attempts have been made over about 24 hours (after 1 min, 5 min, 30 min, 2 h, 5 h, 8 h and 8 h).
- If an endpoint keeps failing for more than 24 hours (20 failed attempts, counted at most once a minute; a problem on our side never counts) we switch it off and e-mail the owners. While it is off, no events are kept for it; once it is back on, “Send again: everything that failed” resends the failures of the last 7 days.
- When 1000 messages are waiting for one endpoint, new ones are recorded as failed instead of queued, and the owners are told once a day.
- Messages may arrive twice or out of order. Use the event id so you never process one twice.
- The dashboard keeps the delivery log of the last 30 days and lets you send any of them again.