Authentication & keys
Every request authenticates with an API key in the Authorization header — no
cookies, no OAuth dance, no signatures:
Authorization: Bearer hr_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXThe full key is displayed once at creation; the platform stores only its SHA-256 hash. Treat it like a password: environment variable or secret manager, never a repository — and never in browser code, where a key would be handed to every visitor.
Verify a new key with GET …/me on its door: it answers with the agency, the
door, whom the key is bound to, the key’s scopes and its budgets.
A key belongs to exactly one door (its audience), fixed when it is created.
The door decides which base URL the key may call at all; a request to another
door answers 403 AUDIENCE_MISMATCH, whatever the scopes say.
| Door | Created in | Bound to | May call |
|---|---|---|---|
agency | Agency console → Partner API | — (the whole agency) | /partner/v1/*, /mcp |
landlord | Landlord portal → API access, or the console on their behalf | one landlord | /partner/v1/landlord/* |
cleaning | Cleaning portal → API access, or the console on their behalf | one cleaning crew | /partner/v1/cleaning/* |
GET /partner/v1/me is the one path every door may call — it is how a key finds
out which door it is.
The binding is the data scope of every request: a landlord key sees the units inside that landlord’s tenures and nothing else, a cleaning key sees that crew’s jobs and nothing else. There is deliberately no parameter to widen it. The agency console lists every key of every door and can revoke any of them.
Scopes
Section titled “Scopes”Each key carries an explicit set of scopes from its door’s catalogue — a
landlord key cannot hold read:guests, because that scope does not exist for
its door. Requests beyond the key’s scopes fail with 403 SCOPE_MISSING; create
keys with the minimum your integration needs.
Plain read scopes are pre-selected when a key is created. Scopes that write or expose personal data are not: they are ticked deliberately or not at all — and an existing key never gains an ability because a new one was added.
Agency door
Section titled “Agency door”| Scope | Grants |
|---|---|
read:units | GET /units, GET /units/:slug, GET /amenities, MCP list_units |
read:availability | GET /availability, MCP get_availability |
read:rates | POST /quote, GET /rates, GET /promotions, GET /fee-types, MCP quote_stay |
read:bookings | GET /bookings, GET /bookings/:id, MCP list_bookings |
read:guests | GET /guests, GET /guests/:id — personal data; also unlocks the contact block on enquiries and the buyer block on invoices |
write:bookings | POST /bookings, POST /bookings/:id/cancel |
read:requests | GET /requests, GET /requests/:id |
read:tasks | GET /tasks, GET /tasks/:id |
write:tasks | POST /tasks, PATCH /tasks/:id |
read:finance | GET /invoices, GET /invoices/:id, GET /payments, GET /deposits |
read:housekeeping | GET /housekeeping/jobs, GET /housekeeping/teams |
read:landlords | GET /landlords, GET /landlords/:id, GET /landlords/:id/statement |
read:costs | GET /unit-costs, GET /cost-categories |
Landlord door
Section titled “Landlord door”| Scope | Grants |
|---|---|
landlord:read:units | GET /units, GET /units/:slug, GET /availability, GET /bookings, GET /bookings/:id |
landlord:read:finance | GET /statement, GET /payouts, GET /invoices |
landlord:read:costs | GET /costs, GET /cost-categories |
landlord:write:costs | POST /costs, DELETE /costs/:id — writes |
landlord:write:blocks | POST /blocks, DELETE /blocks/:id — writes |
landlord:read:documents | GET /documents, GET /documents/:id/download |
landlord:messages | GET /messages, GET /messages/:id, POST /messages, POST /messages/:id/replies — writes |
Cleaning door
Section titled “Cleaning door”| Scope | Grants |
|---|---|
cleaning:read:jobs | GET /units, GET /jobs, GET /plan |
cleaning:write:reports | POST /jobs/:key/start, POST /jobs/:key/finish, POST /jobs/:key/reopen — writes |
Writes through any door are recorded in the agency’s audit trail with the key as the actor, including refused attempts. The MCP endpoint is agency-only and stays read-only.
Rotation & revocation
Section titled “Rotation & revocation”- Rotate by creating a second key, deploying it, then revoking the old one — both stay valid in between, so rotation needs no downtime.
- Revoke any key instantly in the portal that created it, or in the agency console. Revocation is immediate for new requests and permanent.
- Keys can carry an optional expiry date at creation; expired keys answer
401like revoked ones. - The portals show a last used timestamp per key (minute precision), so a forgotten integration is easy to spot — and a dead key easy to retire.
Error responses
Section titled “Error responses”| HTTP | code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | Missing/malformed header, unknown, revoked or expired key, or the agency account is not active |
| 403 | FEATURE_DISABLED | The agency’s plan does not include API access |
| 403 | AUDIENCE_MISMATCH | The key belongs to another door; the response names the door and its base path |
| 403 | SCOPE_MISSING | Key is valid but lacks the scope; the response names it |
| 429 | RATE_LIMITED | Over 60 requests in the rolling minute for this key — respect Retry-After |
| 429 | QUOTA_EXCEEDED | The agency’s 50,000 requests for this month are spent (budgets) |
{ "code": "AUDIENCE_MISMATCH", "title": "This is a landlord key — it can only call /partner/v1/landlord/*", "audience": "landlord", "basePath": "/partner/v1/landlord" }