{"openapi":"3.1.0","info":{"title":"MyRentalCalendar Cleaning API","version":"1.0.0","description":"Where and when to clean, for the dispatch software of an external cleaning company: the crew's units, its derived jobs with windows and deadlines, the suggested day route, and start/finish reports. Every request authenticates with an API key created in the cleaning portal; the key is bound to one crew, so every answer is about that crew's work. No guest names, no prices, no owners — by construction.\n\nAmounts are integers in the agency currency's minor unit. Dates are ISO `YYYY-MM-DD`, and a stay is the half-open range `[arrival, departure)`. Every error carries a stable `code` and a readable `title`."},"servers":[{"url":"https://api.myrentalcalendar.com/partner/v1/cleaning","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Introspection","description":"What this key is"},{"name":"Jobs","description":"Units, jobs and the day route"},{"name":"Reports","description":"Start, finish, reopen"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Your API key: `Authorization: Bearer hr_live_…`"}},"schemas":{"Error":{"type":"object","properties":{"code":{"type":"string","description":"Stable identifier — branch on this","example":"NOT_FOUND"},"title":{"type":"string","description":"Human-readable explanation — log this"}},"required":["code","title"]},"CleaningUnit":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"area":{"type":"string"},"building":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"lat":{"type":"number","nullable":true},"lng":{"type":"number","nullable":true},"bedrooms":{"type":"number"},"bathrooms":{"type":"number"},"sizeSqm":{"type":"number","nullable":true},"checkinTime":{"type":"string"},"checkoutTime":{"type":"string"},"covering":{"type":"boolean","description":"Not the crew's own unit — it covers a job here for another partner"}},"required":["id","name","area","building","address","lat","lng","bedrooms","bathrooms","sizeSqm","checkinTime","checkoutTime","covering"]},"CleaningJob":{"type":"object","properties":{"key":{"type":"string","description":"What the report routes take","example":"turnover:0191a…"},"kind":{"type":"string","enum":["turnover","mid_stay"]},"unitId":{"type":"string"},"date":{"type":"string","description":"First day work is possible"},"endDate":{"type":"string","description":"Last day of the window"},"notBefore":{"type":"string","nullable":true,"description":"Apartment free from here on the first day (HH:MM), late checkout included"},"lateCheckout":{"type":"boolean"},"dueTime":{"type":"string","nullable":true,"description":"Must be finished by then on the last day (HH:MM), early check-in included"},"nextArrival":{"type":"object","nullable":true,"properties":{"date":{"type":"string"},"time":{"type":"string"},"early":{"type":"boolean"}},"required":["date","time","early"]},"flexible":{"type":"boolean","description":"No next stay is waiting — any day of the window works"},"blockDays":{"type":"number"},"blockRule":{"type":"string","nullable":true},"pax":{"type":"number","description":"Head count of the stay — sizing, not identity"},"week":{"type":"number","nullable":true,"description":"Mid-stay only: week 1, 2, … of the running stay"},"status":{"type":"string","enum":["open","in_progress","done","waived"]},"startedAt":{"type":"string","nullable":true},"finishedAt":{"type":"string","nullable":true},"minutes":{"type":"number","nullable":true},"notes":{"type":"string","nullable":true},"covered":{"type":"boolean","description":"Handed to this crew by the office"}},"required":["key","kind","unitId","date","endDate","notBefore","lateCheckout","dueTime","nextArrival","flexible","blockDays","blockRule","pax","week","status","startedAt","finishedAt","minutes","notes","covered"]},"OccupancySpan":{"type":"object","properties":{"id":{"type":"string"},"unitId":{"type":"string"},"arrival":{"type":"string"},"departure":{"type":"string"},"kind":{"type":"string","enum":["stay","blocked"]},"pax":{"type":"number"},"earlyCheckin":{"type":"string","nullable":true},"lateCheckout":{"type":"string","nullable":true}},"required":["id","unitId","arrival","departure","kind","pax","earlyCheckin","lateCheckout"]},"PlanStop":{"allOf":[{"$ref":"#/components/schemas/CleaningJob"},{"type":"object","properties":{"unitName":{"type":"string"},"building":{"type":"string","nullable":true},"area":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"lat":{"type":"number","nullable":true},"lng":{"type":"number","nullable":true},"bedrooms":{"type":"number"},"bathrooms":{"type":"number"},"order":{"type":"number","nullable":true,"description":"Suggested driving order; null when the unit has no coordinates"},"eta":{"type":"string","nullable":true},"slot":{"type":"string","nullable":true,"example":"09:30–11:00"},"atRisk":{"type":"boolean","description":"The deadline cannot be met in this order — a second pair of hands is needed"},"overdue":{"type":"boolean"}},"required":["unitName","building","area","address","lat","lng","bedrooms","bathrooms","order","eta","slot","atRisk","overdue"]}]}},"parameters":{}},"paths":{"/me":{"get":{"tags":["Introspection"],"summary":"Who this key belongs to","description":"No scope needed. Answers which agency the jobs come from, which cleaning company this key is bound to, what the key may do and how much budget is left — wire this up first.","responses":{"200":{"description":"The agency, the key, its binding and its budgets","content":{"application/json":{"schema":{"type":"object","properties":{"audience":{"type":"string","enum":["agency","landlord","cleaning"],"description":"Which door this key opens"},"tenant":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"currency":{"type":"string","example":"AED"},"timezone":{"type":"string","example":"Asia/Dubai"}},"required":["id","name","slug","currency","timezone"]},"key":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","nullable":true},"prefix":{"type":"string","nullable":true},"scopes":{"type":"array","items":{"type":"string","enum":["read:units","read:availability","read:rates","write:rates","read:bookings","read:guests","write:bookings","read:requests","read:tasks","write:tasks","read:finance","read:housekeeping","read:landlords","read:costs","landlord:read:units","landlord:read:finance","landlord:read:costs","landlord:write:costs","landlord:write:blocks","landlord:read:documents","landlord:messages","cleaning:read:jobs","cleaning:write:reports"]}},"createdAt":{"type":"string","nullable":true},"expiresAt":{"type":"string","nullable":true}},"required":["id","name","prefix","scopes","createdAt","expiresAt"]},"landlord":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"description":"Landlord keys: whose apartments and figures this key sees"},"cleaningCompany":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"description":"Cleaning keys: whose jobs this key sees"},"rateLimit":{"type":"object","properties":{"requestsPerMinute":{"type":"number"},"sharedWith":{"type":"array","items":{"type":"string"}}},"required":["requestsPerMinute","sharedWith"]},"quota":{"type":"object","properties":{"month":{"type":"string","example":"2026-08"},"used":{"type":"number"},"quota":{"type":"number"},"remaining":{"type":"number"},"resetsAt":{"type":"string"}},"required":["month","used","quota","remaining","resetsAt"]}},"required":["audience","tenant","key","landlord","cleaningCompany","rateLimit","quota"]}}}},"401":{"description":"Missing, unknown, revoked or expired key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Plan does not include API access, the key belongs to another door, or it lacks the scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-minute budget or monthly quota exhausted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/units":{"get":{"tags":["Jobs"],"summary":"The crew's units","description":"Every apartment the crew is the default cleaner for — what a cleaner needs at the door: name, address, rooms, times. Units it merely covers a job on arrive with that job.","responses":{"200":{"description":"The crew's units","content":{"application/json":{"schema":{"type":"object","properties":{"units":{"type":"array","items":{"$ref":"#/components/schemas/CleaningUnit"}}},"required":["units"]}}}},"401":{"description":"Missing, unknown, revoked or expired key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Plan does not include API access, the key belongs to another door, or it lacks the scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-minute budget or monthly quota exhausted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/jobs":{"get":{"tags":["Jobs"],"summary":"Jobs in a date range","description":"Every turnover and mid-stay cleaning the crew is on duty for whose window touches `[from, to)`, with progress, plus the units involved and anonymous occupancy spans (the reason behind the gaps). Defaults to five weeks from a week ago; at most 92 days.","parameters":[{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"required":false,"name":"from","in":"query"},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"required":false,"name":"to","in":"query"}],"responses":{"200":{"description":"Jobs, units and occupancy in the range","content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string"},"to":{"type":"string"},"units":{"type":"array","items":{"$ref":"#/components/schemas/CleaningUnit"}},"jobs":{"type":"array","items":{"$ref":"#/components/schemas/CleaningJob"}},"stays":{"type":"array","items":{"$ref":"#/components/schemas/OccupancySpan"}}},"required":["from","to","units","jobs","stays"]}}}},"400":{"description":"Range negative or over 92 days","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, unknown, revoked or expired key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Plan does not include API access, the key belongs to another door, or it lacks the scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-minute budget or monthly quota exhausted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/plan":{"get":{"tags":["Jobs"],"summary":"The day plan","description":"The day's stops in suggested driving order with time slots — the same list a cleaner opens in the car. A late checkout delays the start, an early check-in tightens the deadline, and `atRisk` flags a day that needs a second team. Unfinished turnovers from the last 14 days keep appearing until they are reported.","parameters":[{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Default today"},"required":false,"name":"date","in":"query"}],"responses":{"200":{"description":"The plan","content":{"application/json":{"schema":{"type":"object","properties":{"date":{"type":"string"},"stops":{"type":"array","items":{"$ref":"#/components/schemas/PlanStop"}},"routeKm":{"type":"number"}},"required":["date","stops","routeKm"]}}}},"401":{"description":"Missing, unknown, revoked or expired key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Plan does not include API access, the key belongs to another door, or it lacks the scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-minute budget or monthly quota exhausted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/jobs/{key}/start":{"post":{"tags":["Reports"],"summary":"Report a job as started","description":"Starts the clock on a job. Starting twice keeps the first timestamp; a finished job answers 409.","parameters":[{"schema":{"type":"string","pattern":"^(turnover:[0-9a-f-]{36}|midstay:[0-9a-f-]{36}:\\d{4}-\\d{2}-\\d{2})$","description":"`turnover:<bookingId>` or `midstay:<bookingId>:<YYYY-MM-DD>`","example":"turnover:0191a3f0-…"},"required":true,"name":"key","in":"path"}],"responses":{"200":{"description":"Already started — the original time","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"startedAt":{"type":"string"}},"required":["ok","startedAt"]}}}},"201":{"description":"Started now","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"startedAt":{"type":"string"}},"required":["ok","startedAt"]}}}},"401":{"description":"Missing, unknown, revoked or expired key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Plan does not include API access, the key belongs to another door, or it lacks the scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such job of this crew","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already reported as finished","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-minute budget or monthly quota exhausted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/jobs/{key}/finish":{"post":{"tags":["Reports"],"summary":"Report a job as finished","description":"Marks the job done and mirrors it into what the office watches — the cleaned badge on the calendar, the readiness lane on the ops board. Finishing without a start is fine; `minutes` is then null.","parameters":[{"schema":{"type":"string","pattern":"^(turnover:[0-9a-f-]{36}|midstay:[0-9a-f-]{36}:\\d{4}-\\d{2}-\\d{2})$","description":"`turnover:<bookingId>` or `midstay:<bookingId>:<YYYY-MM-DD>`","example":"turnover:0191a3f0-…"},"required":true,"name":"key","in":"path"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"notes":{"type":"string","nullable":true,"maxLength":500}}}}}},"responses":{"200":{"description":"Finished","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"finishedAt":{"type":"string"},"minutes":{"type":"number","nullable":true}},"required":["ok","finishedAt","minutes"]}}}},"401":{"description":"Missing, unknown, revoked or expired key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Plan does not include API access, the key belongs to another door, or it lacks the scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such job of this crew","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-minute budget or monthly quota exhausted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/jobs/{key}/reopen":{"post":{"tags":["Reports"],"summary":"Undo a finish","description":"Clears the finish report and the mirrored confirmation. A cleaning the office marked as declined by the guest is not the crew's to undo.","parameters":[{"schema":{"type":"string","pattern":"^(turnover:[0-9a-f-]{36}|midstay:[0-9a-f-]{36}:\\d{4}-\\d{2}-\\d{2})$","description":"`turnover:<bookingId>` or `midstay:<bookingId>:<YYYY-MM-DD>`","example":"turnover:0191a3f0-…"},"required":true,"name":"key","in":"path"}],"responses":{"200":{"description":"Reopened","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]}}}},"401":{"description":"Missing, unknown, revoked or expired key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Plan does not include API access, the key belongs to another door, or it lacks the scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such job of this crew, or nothing to reopen","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-minute budget or monthly quota exhausted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}