Meeting Rooms API
This section of the DeskOS API covers meeting rooms: list rooms, check availability, price a slot, book it and cancel it from an outside system such as a tenant's own booking portal, a room display or kiosk, an automation tool like n8n or Zapier, a chatbot, or an AI agent.
Everything you book shows up in the DeskOS admin console exactly as if a staff member had booked it, with the same pricing, credits, bill-to-account lines, door access and cancellation policy.
Getting a key, sending it, campus rules, the response shape and the errors every section shares are all on the Authentication page. This page only covers what is specific to meeting rooms.
Read the general agent instructions on the Authentication page, then the meeting-room specific rules below.
Base path
All paths on this page are relative to:
https://oscar.nova.deskos.net/api/v1/space-management/external/
Your Developer Settings page shows the exact base URL for your workspace.
Permissions
Meeting room permissions all start with meeting_rooms:. An admin ticks them per key on the Developer Settings page. A key only gets meeting room permissions if the admin creating it holds Meeting Rooms: settings themselves.
| Permission | Unlocks |
|---|---|
meeting_rooms:rooms:read | GET rooms/, GET rooms/<id>/, GET rooms/<id>/availability/, GET rooms/<id>/quote/ |
meeting_rooms:bookings:read | GET bookings/, GET bookings/<id>/ |
meeting_rooms:bookings:create | POST bookings/ |
meeting_rooms:bookings:cancel | GET bookings/<id>/cancel/ (preview) and POST bookings/<id>/cancel/ |
A read-only room display needs meeting_rooms:rooms:read. A booking portal needs all four. A "cancel when the visitor cancels" automation needs meeting_rooms:bookings:read and meeting_rooms:bookings:cancel.
GET health/ needs no permission and works with any key. Use it to confirm the key carries the meeting_rooms: permissions you expect before doing anything else.
Campuses
The general campus rules are on the Authentication page. For this section:
- A room or booking on a campus the key cannot reach is a 404, not a 403, so nothing leaks about what exists elsewhere.
- List endpoints accept
?campus_id=to narrow results. A campus the key cannot reach answers 404. - There is nothing to resolve on create: a room already belongs to one campus, so the key's default campus is not used here.
Times and money
- Datetimes are ISO 8601. A value with an offset (
2026-09-15T14:00:00+05:30or...Z) is used as sent. A naive value (2026-09-15T14:00:00) is read in the room's campus timezone. Responses always carry the campus offset. - Every room and booking reports its
currency. Money fields are strings with two decimals in that currency. Do not assume a symbol. - A booking must start and end on the same campus day.
Reference values
Booking statuses: confirmed, pending_payment, pending_approval, cancelled, expired, no_show. Bookings made through this API are always confirmed.
Time states (derived from the clock): upcoming, in_progress, completed, cancelled.
Payment methods on create: bill_to_account (default), credits.
API endpoints
| Method | Path | Permission | Purpose |
|---|---|---|---|
| GET | health/ | none | What this key is and can do |
| GET | rooms/ | meeting_rooms:rooms:read | List rooms |
| GET | rooms/<id>/ | meeting_rooms:rooms:read | One room |
| GET | rooms/<id>/availability/ | meeting_rooms:rooms:read | Free slots for a date |
| GET | rooms/<id>/quote/ | meeting_rooms:rooms:read | Price a window |
| GET | bookings/ | meeting_rooms:bookings:read | List bookings |
| POST | bookings/ | meeting_rooms:bookings:create | Create a booking |
| GET | bookings/<id>/ | meeting_rooms:bookings:read | One booking |
| GET | bookings/<id>/cancel/ | meeting_rooms:bookings:cancel | Cancellation preview |
| POST | bookings/<id>/cancel/ | meeting_rooms:bookings:cancel | Cancel |
1. List rooms
GET rooms/?campus_id=7
| Parameter | Notes |
|---|---|
campus_id | Optional. Only rooms on this campus |
include_inactive | 1 to include rooms that are not active. Default: active rooms only |
{
"success": true,
"count": 2,
"rooms": [
{
"id": 12,
"name": "Board Room",
"description": "Seats eight around one table.",
"campus": {"id": 7, "name": "Acme North", "timezone": "Asia/Kolkata", "currency": "INR"},
"floor": "1",
"capacity": 8,
"status": "active",
"amenities": ["TV", "Whiteboard", "Video conferencing"],
"images": ["https://cdn.example/rooms/board.jpg"],
"currency": "INR",
"pricing": {
"is_free": false,
"member_hourly_rate": "1000.00",
"non_member_hourly_rate": "1500.00",
"has_non_member_rate": true,
"hourly_credit_rate": "10.00",
"can_use_credits": true
},
"booking_rules": {
"minimum_minutes": 30,
"maximum_minutes": 240,
"slot_minutes": 30,
"advance_booking_days": 30
}
}
]
}
pricing.member_hourly_rate applies to members with an active plan; non_member_hourly_rate applies to everyone else when the room has one. booking_rules are enforced on create, so read them before offering times to a user.
2. Retrieve a room
GET rooms/12/
Returns {"success": true, "room": {...}} with the same shape as the list.
3. Availability
GET rooms/12/availability/?date=2026-09-15
| Parameter | Notes |
|---|---|
date | YYYY-MM-DD in the campus timezone. Default: today |
{
"success": true,
"room": {"id": 12, "name": "Board Room"},
"date": "2026-09-15",
"timezone": "Asia/Kolkata",
"is_holiday": false,
"holiday_name": null,
"is_closed": false,
"working_hours": {"opens": "09:00", "closes": "18:00"},
"slot_minutes": 30,
"minimum_minutes": 30,
"booked": [
{"booking_id": 881, "start_time": "2026-09-15T10:00:00+05:30", "end_time": "2026-09-15T11:00:00+05:30", "status": "confirmed"}
],
"slots": [
{"start_time": "2026-09-15T09:00:00+05:30", "end_time": "2026-09-15T09:30:00+05:30"},
{"start_time": "2026-09-15T09:30:00+05:30", "end_time": "2026-09-15T10:00:00+05:30"},
{"start_time": "2026-09-15T11:00:00+05:30", "end_time": "2026-09-15T11:30:00+05:30"}
]
}
Slots are slot_minutes apart and each is minimum_minutes long: the same grid members book on in the DeskOS app. To offer a longer booking, join consecutive slots. booked includes confirmed bookings and unpaid holds still inside their hold window. A holiday or closed weekday answers is_closed: true with no slots. working_hours is null when the campus has none configured, in which case the whole day is open.
4. Quote
GET rooms/12/quote/?start_time=2026-09-15T14:00:00&end_time=2026-09-15T16:00:00&member_email=priya@tenant.example
| Parameter | Required | Notes |
|---|---|---|
start_time, end_time | yes | ISO 8601 |
member_email or member_id | no | Prices at the member rate when this member holds an active plan. Without it the non-member rate is quoted where the room has one |
{
"success": true,
"room": {"id": 12, "name": "Board Room"},
"start_time": "2026-09-15T14:00:00+05:30",
"end_time": "2026-09-15T16:00:00+05:30",
"is_available": true,
"slot_free": true,
"rule_violation": null,
"member": {"id": "3f2b6c14-77a1-4d6e-8a19-9c0e7b5a1234", "email": "priya@tenant.example"},
"quote": {
"hourly_rate": "1000.00",
"member_rate_applied": true,
"hours": "2.00",
"base_amount": "2000.00",
"tax_amount": "360.00",
"total_amount": "2360.00",
"currency": "INR",
"credits_required": "20.00"
}
}
is_available is true only when the slot is free and the window breaks no rule. When a rule fails, rule_violation carries the same message a create would return. credits_required is null on rooms that do not take credits. A quote reserves nothing.
5. List bookings
GET bookings/?room_id=12&time_state=upcoming&page_size=50
| Parameter | Notes |
|---|---|
campus_id | Bookings on one campus |
room_id | Bookings of one room |
member_email or member_id | Bookings made for one member |
status | Comma-separated statuses, for example confirmed,pending_payment |
time_state | One of upcoming, in_progress, completed, cancelled |
date_from, date_to | YYYY-MM-DD, inclusive, by start time |
page, page_size | Page size up to 100, default 25 |
{
"success": true,
"count": 3,
"page": 1,
"page_size": 25,
"has_next": false,
"bookings": [{"id": 902, "...": "..."}]
}
Newest start time first. Expired unpaid holds are not listed.
6. Create a booking
POST bookings/
| Field | Required | Notes |
|---|---|---|
room_id | yes | An active room the key can reach |
start_time | yes | ISO 8601 |
end_time | yes | ISO 8601, after start_time, same campus day |
member_email or member_id | yes | The member the room is booked for. member_id is the member UUID the Members API returns. Must belong to your organisation and to a company; the booking is billed to that company |
payment_method | no | bill_to_account (default) or credits |
notes | no | Shown to staff on the booking. Up to 2,000 characters |
Rules enforced (each answers 400 with the field named): not in the past, within the room's advance_booking_days, at least minimum_minutes and at most maximum_minutes long, inside the campus working hours, not on a holiday, room active. A slot already taken answers 409.
Payment. With bill_to_account the whole amount is added to the company's account as an approved bill-to-account line, with tax resolved from the campus's billing centre. With credits the room must accept credits; the member's own allocation is used first, then the company pool, and any shortfall is billed to the account. A free-use room costs nothing either way.
The booking is created confirmed, recorded as an admin booking, with the staff account that created the key as the booker of record. Door access is granted where Spintly is configured, and admins get an in-app notification.
curl -X POST https://oscar.nova.deskos.net/api/v1/space-management/external/bookings/ \
-H "Authorization: Api-Key dk_live_..." \
-H "Content-Type: application/json" \
-d '{
"room_id": 12,
"start_time": "2026-09-15T14:00:00",
"end_time": "2026-09-15T16:00:00",
"member_email": "priya@tenant.example",
"notes": "Client pitch"
}'
Response 201:
{
"success": true,
"booking": {
"id": 902,
"room": {"id": 12, "name": "Board Room", "campus": {"id": 7, "name": "Acme North", "timezone": "Asia/Kolkata", "currency": "INR"}},
"member": {"id": "3f2b6c14-77a1-4d6e-8a19-9c0e7b5a1234", "name": "Priya Shah", "email": "priya@tenant.example"},
"company": {"id": 40, "name": "Tenant Co"},
"start_time": "2026-09-15T14:00:00+05:30",
"end_time": "2026-09-15T16:00:00+05:30",
"total_minutes": 120,
"status": "confirmed",
"time_state": "upcoming",
"time_state_label": "Upcoming",
"is_cancelled": false,
"payment": {
"method": "bill_to_account",
"status": "bill_to_account",
"currency": "INR",
"amount": "2000.00",
"tax": "360.00",
"total": "2360.00",
"credits_used": "0.00"
},
"notes": "Client pitch",
"checked_in_at": null,
"checked_out_at": null,
"cancelled_at": null,
"cancellation_reason": "",
"cancellation": null,
"created_at": "2026-09-12T18:20:11+05:30"
},
"quote": {"hourly_rate": "1000.00", "member_rate_applied": true, "hours": "2.00", "base_amount": "2000.00", "tax_amount": "360.00", "total_amount": "2360.00", "currency": "INR", "credits_required": "20.00"}
}
7. Retrieve a booking
GET bookings/902/
Returns {"success": true, "booking": {...}}. After a cancellation, cancellation carries the refund record:
"cancellation": {
"id": "2f1c...",
"refund_percentage": "50.00",
"refund_amount": "1000.00",
"credits_refunded": "0.00",
"reason": "Client cancelled",
"full_refund_override": false,
"requested_at": "2026-09-14T09:12:40+05:30"
}
8. Cancel a booking
Preview first:
GET bookings/902/cancel/
{
"success": true,
"booking_id": 902,
"can_cancel": true,
"policy_refund": {
"refund_percentage": "50.00",
"credits_refund": "0.00",
"tier": "24 hours before",
"tier_description": "Cancel at least 24 hours before the start for a 50% refund",
"deadline": "2026-09-14T14:00:00+05:30"
},
"full_refund": {"credits_refund": "20.00"}
}
Then cancel:
POST bookings/902/cancel/
{"reason": "Client cancelled"}
| Field | Required | Notes |
|---|---|---|
reason | for full | Stored on the booking and the refund record. Up to 1,000 characters |
refund | no | policy (default) applies the campus cancellation policy. full overrides it with a full refund and needs a reason |
Response 200 carries the updated booking and a refund summary (type, refund_percentage, credits_refunded, refund_amount). Pending bill-to-account lines are voided, credits go back to the pool they came from, and door access is revoked.
A booking that has already started cannot be cancelled here (400, and the preview says can_cancel: false). An already cancelled booking answers 409.
Errors specific to this section
Authentication, missing permission, suspension and rate limit errors are the same in every section and are listed on the Authentication page. The ones below are specific to meeting rooms.
400: Validation error
{"success": false, "errors": {"member_email": "No member with this email in your organisation."}}
{"success": false, "errors": {"end_time": "This room allows bookings of at most 240 minutes."}}
403: Missing permission
{"detail": "This API key does not have the 'meeting_rooms:bookings:create' scope."}
404: Not found
The room, booking or campus does not exist or is on a campus this key cannot reach.
409: Conflict
The slot is already taken (on create), or the booking is already cancelled (on cancel).
Integration examples
Direct HTTP call: cURL
BASE=https://oscar.nova.deskos.net/api/v1/space-management/external
KEY="dk_live_..."
# Free slots tomorrow
curl "$BASE/rooms/12/availability/?date=2026-09-15" -H "Authorization: Api-Key $KEY"
# Book one of them
curl -X POST "$BASE/bookings/" \
-H "Authorization: Api-Key $KEY" -H "Content-Type: application/json" \
-d '{"room_id": 12, "start_time": "2026-09-15T11:00:00", "end_time": "2026-09-15T12:00:00", "member_email": "priya@tenant.example"}'
Direct HTTP call: Python
import os
import requests
BASE = "https://oscar.nova.deskos.net/api/v1/space-management/external/"
HEADERS = {"Authorization": f"Api-Key {os.environ['DESKOS_API_KEY']}"}
slots = requests.get(BASE + "rooms/12/availability/", params={"date": "2026-09-15"}, headers=HEADERS).json()
first = slots["slots"][0]
quote = requests.get(BASE + "rooms/12/quote/", headers=HEADERS, params={
"start_time": first["start_time"], "end_time": first["end_time"], "member_email": "priya@tenant.example",
}).json()
if quote["is_available"]:
booking = requests.post(BASE + "bookings/", headers=HEADERS, json={
"room_id": 12, "start_time": first["start_time"], "end_time": first["end_time"],
"member_email": "priya@tenant.example",
}).json()
print(booking["booking"]["id"], booking["booking"]["payment"]["total"], booking["booking"]["payment"]["currency"])
Direct HTTP call: JavaScript / Node.js
const BASE = 'https://oscar.nova.deskos.net/api/v1/space-management/external/';
const headers = { Authorization: `Api-Key ${process.env.DESKOS_API_KEY}`, 'Content-Type': 'application/json' };
const res = await fetch(`${BASE}bookings/`, {
method: 'POST',
headers,
body: JSON.stringify({
room_id: 12,
start_time: '2026-09-15T14:00:00',
end_time: '2026-09-15T16:00:00',
member_email: 'priya@tenant.example',
}),
});
const data = await res.json();
if (!data.success) throw new Error(JSON.stringify(data.errors));
console.log(data.booking.id, data.booking.payment.total);
n8n / Zapier / Make
Use an HTTP Request node with the header Authorization: Api-Key dk_live_.... Typical flows: a form submission calls quote/ then bookings/; a calendar cancellation calls bookings/<id>/cancel/; a room display polls availability/ every few minutes.
Instructions for AI agents
The general rules (how a key is obtained, how to handle it, what to do on 401 and 403) are on the Authentication page. Read those first. This section adds what is specific to meeting rooms.
Permissions to ask for
Ask only for the meeting_rooms: permissions the task needs.
- A room display:
meeting_rooms:rooms:readalone. - A booking flow:
meeting_rooms:rooms:read,meeting_rooms:bookings:readandmeeting_rooms:bookings:create. - Add
meeting_rooms:bookings:cancelonly if cancelling is part of the job.
The human also decides whose account the key is created under. That account is recorded as the staff member who made every booking and cancellation through the key.
Recommended first steps
- Call
GET health/. It confirms the key works and returnsscopesandallowed_campuses. Use the answer to decide whether every operation you plan is in scope before doing anything else. - Call
GET rooms/and cache the room ids,booking_rulesandpricing. Ids are stable. - Before offering a time to a user, call
availability/for that date and offer only the returned slots (joined when the user wants longer). Do not compute slots from working hours yourself. - Call
quote/with the member's email before booking and show thetotal_amountandcurrencyto whoever is paying. Book only whenis_availableistrue. - Keep the returned booking
idalongside your own record. It is what staff and the member will see.
Behaviour rules
- Money is real. Every booking you create is billed to a company's account or deducted from its credits the moment it is confirmed. Never create a booking to "see what happens". Use
quote/for that. - Do not create duplicates. There is no server-side deduplication beyond the slot itself. If a create call times out, list bookings for that room and window before retrying.
- Book for the right person.
member_emailmust be the member the room is actually for. Do not fall back to an arbitrary member to make the call succeed. - Do not cancel bookings you did not create unless the human has explicitly asked for that workflow. Always preview with
GET cancel/and show the refund outcome before aPOST. Never userefund: fullon your own initiative; it overrides the operator's policy and must be a human decision. - Always send a meaningful
reasonwhen cancelling. It is stored on the booking and on the refund record. - Respect the daily limit.
health/showsrequests_todayagainstdaily_limit. The limit is shared by every section the key can reach. Pollavailability/at a sensible interval and page lists withpage_sizeup to 100. - Treat 404 on a room or booking as "not visible to this key", not proof it never existed. It may be on a campus the key cannot reach.
- Use the campus timezone. Send naive times only when you mean campus-local time. When in doubt send an explicit offset.
Reporting back to the human
When you finish, tell the human:
- the key name you used (never the key itself),
- which permissions it turned out to need,
- the ids of any bookings you created or cancelled, with amounts and currency,
- any 403, 409 or 429 you hit, so they can adjust the key or the flow.
Frequently asked questions
Can I book for someone who is not a DeskOS member? Not through this API. The booking needs a member of your organisation who belongs to a company, because that company is billed. Create the member first (from the admin console) or book under a house account.
Can I hold a slot without confirming?
No. This API creates confirmed bookings. Use quote/ to price a slot and book only when the user commits. Slots are not reserved by quoting.
Which rate does a member get?
Members with an active plan pay member_hourly_rate. Everyone else pays non_member_hourly_rate where the room has one. quote/ tells you which applied with member_rate_applied.
Can the API tell me when a booking changes?
Not yet. Poll GET bookings/<id>/ or the list endpoint with a time_state filter. Outbound webhooks are planned.
Can the same key also raise service requests?
Yes, if the admin ticked helpdesk: permissions on it as well. One key can span several sections. See Service Requests.
Quick reference
Base: https://oscar.nova.deskos.net/api/v1/space-management/external/
Auth: Authorization: Api-Key dk_live_...
GET health/
GET rooms/?campus_id=&include_inactive=
GET rooms/<id>/
GET rooms/<id>/availability/?date=YYYY-MM-DD
GET rooms/<id>/quote/?start_time=&end_time=&member_email=
GET bookings/?campus_id=&room_id=&member_email=&status=&time_state=&date_from=&date_to=&page=&page_size=
POST bookings/ {room_id, start_time, end_time, member_email, payment_method?, notes?}
GET bookings/<id>/
GET bookings/<id>/cancel/
POST bookings/<id>/cancel/ {reason?, refund?: policy | full}
Permissions: meeting_rooms:rooms:read, meeting_rooms:bookings:read,
meeting_rooms:bookings:create, meeting_rooms:bookings:cancel
Statuses: confirmed, pending_payment, pending_approval, cancelled, expired, no_show
Time states: upcoming, in_progress, completed, cancelled
Getting help
API keys, permissions, campus restrictions, revocation and the access log are all self-service from Developer Settings in your admin console (avatar menu, top right). For a higher daily limit or anything else, reach the DeskOS team through your usual support channel.
- Email: sales@deskos.net
- Phone: +91 742810 9069