Skip to main content

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.

Read Authentication first

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.

Building with an AI agent or coding assistant?

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.

PermissionUnlocks
meeting_rooms:rooms:readGET rooms/, GET rooms/<id>/, GET rooms/<id>/availability/, GET rooms/<id>/quote/
meeting_rooms:bookings:readGET bookings/, GET bookings/<id>/
meeting_rooms:bookings:createPOST bookings/
meeting_rooms:bookings:cancelGET 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:30 or ...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​

MethodPathPermissionPurpose
GEThealth/noneWhat this key is and can do
GETrooms/meeting_rooms:rooms:readList rooms
GETrooms/<id>/meeting_rooms:rooms:readOne room
GETrooms/<id>/availability/meeting_rooms:rooms:readFree slots for a date
GETrooms/<id>/quote/meeting_rooms:rooms:readPrice a window
GETbookings/meeting_rooms:bookings:readList bookings
POSTbookings/meeting_rooms:bookings:createCreate a booking
GETbookings/<id>/meeting_rooms:bookings:readOne booking
GETbookings/<id>/cancel/meeting_rooms:bookings:cancelCancellation preview
POSTbookings/<id>/cancel/meeting_rooms:bookings:cancelCancel

1. List rooms​

GET rooms/?campus_id=7
ParameterNotes
campus_idOptional. Only rooms on this campus
include_inactive1 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
ParameterNotes
dateYYYY-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
ParameterRequiredNotes
start_time, end_timeyesISO 8601
member_email or member_idnoPrices 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
ParameterNotes
campus_idBookings on one campus
room_idBookings of one room
member_email or member_idBookings made for one member
statusComma-separated statuses, for example confirmed,pending_payment
time_stateOne of upcoming, in_progress, completed, cancelled
date_from, date_toYYYY-MM-DD, inclusive, by start time
page, page_sizePage 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/
FieldRequiredNotes
room_idyesAn active room the key can reach
start_timeyesISO 8601
end_timeyesISO 8601, after start_time, same campus day
member_email or member_idyesThe 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_methodnobill_to_account (default) or credits
notesnoShown 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"}
FieldRequiredNotes
reasonfor fullStored on the booking and the refund record. Up to 1,000 characters
refundnopolicy (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:read alone.
  • A booking flow: meeting_rooms:rooms:read, meeting_rooms:bookings:read and meeting_rooms:bookings:create.
  • Add meeting_rooms:bookings:cancel only 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.

  1. Call GET health/. It confirms the key works and returns scopes and allowed_campuses. Use the answer to decide whether every operation you plan is in scope before doing anything else.
  2. Call GET rooms/ and cache the room ids, booking_rules and pricing. Ids are stable.
  3. 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.
  4. Call quote/ with the member's email before booking and show the total_amount and currency to whoever is paying. Book only when is_available is true.
  5. Keep the returned booking id alongside 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_email must 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 a POST. Never use refund: full on your own initiative; it overrides the operator's policy and must be a human decision.
  • Always send a meaningful reason when cancelling. It is stored on the booking and on the refund record.
  • Respect the daily limit. health/ shows requests_today against daily_limit. The limit is shared by every section the key can reach. Poll availability/ at a sensible interval and page lists with page_size up 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.