Lazybee Partner API
Programmatic access to Lazybee co-living inventory: properties, room listings with media and features, live availability calendars, partner rates, booking-request intake and signed webhooks. JSON over HTTPS, versioned as v1, dates in ISO 8601, amounts in SGD.
Base URL and authentication
All endpoints live under https://www.lazybee.sg/api/v1 (the bare domain redirects here; use the www host in scripts so plain curl works without following redirects). Every request carries your API key in the Authorization header. Keys are issued per partner and can be revoked or rate-limited independently.
curl -s https://www.lazybee.sg/api/v1/ping \
-H "Authorization: Bearer $LAZYBEE_API_KEY"
{"ok":true,"partner":"Your Platform","version":"v1"}Properties
GET /properties lists our buildings. GET /properties/{slug} returns one.
{
"slug": "ivory-heights",
"profile": { "title": "Ivory Heights", "description": "..." },
"media": [ { "url": "https://...jpg" } ],
"features": [ "pool", "gym", "near-mrt" ],
"listing_count": 7,
"links": {
"canonical": "https://lazybee.sg/properties/ivory-heights",
"book": "https://book.lazybee.sg"
},
"updated_at": "2026-08-01T02:11:09Z"
}Listings
GET /listings returns every lettable room. Filter with property, available_from and max_rate. Rates are quoted per month for a default 12-month stay; pass duration_months (3 to 36; values outside that range are clamped to it) to quote a different length. GET /listings/{code} returns one room. Codes are uppercase; lowercase is accepted and normalised.
{
"code": "IH-STD1",
"property": "ivory-heights",
"profile": { "title": "Standard Room 1", "description": "..." },
"media": [ { "url": "https://...jpg" } ],
"features": [ "aircon", "window" ],
"rate_card": {
"monthly_rate": 1500,
"deposit": 1500,
"min_stay_months": 3,
"currency": "SGD",
"duration_months": 12
},
"available_from": "2026-09-01",
"max_occupancy": 2,
"links": {
"canonical": "https://lazybee.sg/rooms/IH-STD1",
"book": "https://book.lazybee.sg"
},
"updated_at": "2026-08-05T11:40:00Z"
}Availability calendar
GET /listings/{code}/calendar returns date windows one year ahead. A window is open or unavailable and carries nothing else: no occupant information, by design.
{
"listing": "IH-STD1",
"from": "2026-08-12",
"horizon_days": 365,
"windows": [
{ "start": "2026-08-12", "end": "2026-08-31", "status": "open" },
{ "start": "2026-09-01", "end": "2026-12-14", "status": "unavailable" },
{ "start": "2026-12-15", "end": "2027-08-12", "status": "open" }
]
}Booking requests
Push a tenant to us with POST /booking-requests. Required: listing_code, move_in (ISO YYYY-MM-DD; other date formats are rejected with 422), duration_months (a whole number), applicant.name and applicant.email. Include an idempotency_key so safe retries never create duplicates. Status moves through received, in_review, then confirmed or declined; poll GET /booking-requests/{id} or subscribe to the booking_request.updated webhook.
curl -s -X POST https://www.lazybee.sg/api/v1/booking-requests \
-H "Authorization: Bearer $LAZYBEE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"listing_code": "IH-STD1",
"move_in": "2026-10-01",
"duration_months": 6,
"idempotency_key": "your-ref-123",
"applicant": { "name": "Jane Tan", "email": "jane@example.com", "phone": "+65..." }
}'
{"id":"1f4c...","listing_code":"IH-STD1","status":"received","created_at":"..."}Bookings
Where a booking request is a lead we review, POST /bookings places a confirmed hold that immediately blocks the calendar. Provide starts_on and optionally ends_on (omit for open-ended), both strictly ISO YYYY-MM-DD (anything else is rejected with 422, never coerced), plus guest.name, your own external_ref, and an idempotency_key for safe retries. Manage with GET /bookings, GET /bookings/{id} and POST /bookings/{id}/cancel. Every create and status change emits the booking.updated webhook to you.
curl -s -X POST https://www.lazybee.sg/api/v1/bookings \
-H "Authorization: Bearer $LAZYBEE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"listing_code": "IH-STD1",
"starts_on": "2026-10-01",
"ends_on": "2027-03-31",
"external_ref": "your-booking-8891",
"idempotency_key": "your-booking-8891",
"guest": { "name": "Jane Tan", "email": "jane@example.com" }
}'
{"id":"...","listing_code":"IH-STD1","starts_on":"2026-10-01","ends_on":"2027-03-31",
"status":"confirmed","external_ref":"your-booking-8891","created_at":"..."}Webhooks
Register an HTTPS endpoint with POST /webhooks and choose from five events: listing.calendar.updated, listing.rates.updated, listing.profile.updated, booking_request.updated and booking.updated. The response includes your signing secret, shown once. Payloads are pointers; re-read the API for current state. Deliveries retry with backoff for up to eight attempts.
// Verify the Lazybee-Signature header (t=<unix>,v1=<hmac>)
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, body, header) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header ?? "");
if (!m) return false;
const mac = createHmac("sha256", secret).update(m[1] + "." + body).digest("hex");
return timingSafeEqual(Buffer.from(mac, "hex"), Buffer.from(m[2], "hex"));
}Rate limits and errors
Default limit is 60 requests per minute per key; excess returns HTTP 429 with a Retry-After header. Pace bursts and back off on 429: sustained rapid bursts from one address can also trip our host's own network protection, which answers with an HTML challenge no server can pass. Errors always use one envelope:
{ "error": { "code": "validation_failed", "message": "Missing: move_in" } }Request access
Keys are issued directly by our team. Write to mark@lazybee.sg with your platform name and what you plan to build, and we will set you up with a key and, where relevant, your contracted rate card.