Developers

OhMyDesk Booking API

Read a space's live availability and create desk, meeting-room, whole-day and private-office bookings straight from your own website, widget, or automations. Served from ohmydesk.app — no backend URLs to wire up.

Base URL

https://ohmydesk.app/api/v1

Format

JSON, envelope { data, error, meta }

Auth

Public endpoints need no key; the rest use a per-space API key

Authentication

There are two tiers. Public endpoints — availability and desk bookings for a space — take no key and are safe to call from a browser or a public website, since they expose only what a space already publishes on its booking page. Everything else is authenticated with a per-organization API key.

Create and revoke keys in Settings → Integrations → API (owners and admins). Send the key as a bearer token — or the x-api-key header:

Authorization: Bearer omd_live_…
Keep API keys secret. A key carries operator-level access to your space's booking data. Never embed one in a public website or client-side JavaScript — use the keyless public endpoints there instead.

Get public availability

GET/public/{slug}/availability

Returns the space's desks (grouped by room), meeting rooms, private offices, and the currently booked slots. slug is the space's public booking slug — the last segment of its /book/<slug> page. This is the same read the public booking page and the embeddable widget use.

curl https://ohmydesk.app/api/v1/public/your-space/availability

The data object contains org, rooms[] (each with desks[] carrying deskId and label), meetingRooms[], privateOffices[], and bookedSlots[] (each { deskId, date }).

Create a desk booking

POST/public/{slug}/bookings

Books a free desk for each requested date. A desk is auto-assigned per date from live availability — you don't pick a specific desk. The space's operator is notified (and the visitor too, if you include an email), exactly as if the booking came through the space's own page.

curl -X POST https://ohmydesk.app/api/v1/public/your-space/bookings \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: booking-2026-07-20-ada" \
  -d '{
    "dates": ["2026-07-20", "2026-07-21"],
    "visitorName": "Ada Lovelace",
    "visitorEmail": "ada@example.com",
    "visitorPhone": "+359 888 123 456",
    "notes": "Prefers a quiet corner"
  }'

Body

Response201 Created

{
  "data": {
    "bookings": [
      { "date": "2026-07-20", "deskId": "room2-desk4", "deskLabel": "Desk 8" },
      { "date": "2026-07-21", "deskId": "room1-desk2", "deskLabel": "Desk 2" }
    ]
  },
  "error": null,
  "meta": { "slug": "your-space" }
}

Book a meeting room (hourly)

POST/public/{slug}/meeting-room-bookings

Books a meeting room for a time slot on one day. Times are minutes from midnight (600 = 10:00, 660 = 11:00), aligned to the room's slot size and within its opening hours — read the room's slotMinutes, openMinute and closeMinute from availability. An email is required. Pass companyName / companyAddress / companyVatId for a B2B booking (a draft invoice is created).

curl -X POST https://ohmydesk.app/api/v1/public/your-space/meeting-room-bookings \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: mr-2026-07-20-ada" \
  -d '{
    "meetingRoomId": "1ef38266-7b46-415d-b6f4-690c7673e382",
    "date": "2026-07-20",
    "startMinute": 600,
    "endMinute": 660,
    "visitorName": "Ada Lovelace",
    "visitorEmail": "ada@example.com"
  }'

Response201 Created

{
  "data": {
    "booking": {
      "success": true,
      "booking_id": "…",
      "price": 40,
      "currency": "EUR",
      "duration_minutes": 60,
      "start_time": "2026-07-20T07:00:00+00:00",
      "end_time": "2026-07-20T08:00:00+00:00"
    }
  },
  "error": null,
  "meta": { "slug": "your-space" }
}

Book a room for whole days

POST/public/{slug}/whole-day-bookings

Books a whole-day-enabled room across a date range (startDateendDate, max 60 days). When the space books whole-day rooms for free, this returns 201 with a booking_group_id. When the space takes card payment online, it returns 200 with a checkoutUrl instead (a visitorEmail is required) — see Paid resources.

curl -X POST https://ohmydesk.app/api/v1/public/your-space/whole-day-bookings \
  -H "Content-Type: application/json" \
  -d '{
    "meetingRoomId": "…",
    "startDate": "2026-07-20",
    "endDate": "2026-07-22",
    "visitorName": "Ada Lovelace",
    "visitorEmail": "ada@example.com"
  }'

Request a private office

POST/public/{slug}/office-requests

Registers interest in a private office — normally this creates a lead for the operator to follow up, not a confirmed booking (202 Accepted, data.received: true). companyName is required. When the space takes card payment online for an available office, it returns 200 with a checkoutUrl instead (an email is required, and paying the first month reserves the office) — see Paid resources.

curl -X POST https://ohmydesk.app/api/v1/public/your-space/office-requests \
  -H "Content-Type: application/json" \
  -d '{
    "officeId": "…",
    "companyName": "Difference Engine Ltd",
    "email": "ada@example.com",
    "phone": "+359 888 123 456",
    "preferredStart": "2026-08-01",
    "message": "Team of 4, need parking",
    "returnUrl": "https://your-site.com/booking/done"
  }'

Whole-day rooms and private offices that a space has set to take card payment online return 200 with a Stripe checkoutUrl instead of confirming immediately. Redirect the buyer there; once they pay, OhMyDesk creates the booking (or opens the office agreement) and notifies the operator, exactly as a card booking on the space's own page would. The response carries paymentRequired: true so you can branch on it:

{
  "data": {
    "checkoutUrl": "https://checkout.stripe.com/c/pay/cs_test_…",
    "paymentRequired": true
  },
  "error": null,
  "meta": { "slug": "your-space" }
}

Hourly meeting-room bookings and desk bookings are always created directly (no checkout).

Idempotency

Send an Idempotency-Key header with any booking POST to make it safe to retry. The first request with a given key does the work and stores its result; any later request with the same key replays that stored response instead of creating a second booking. Use a fresh, unique key for each distinct booking attempt (a UUID works well).

Without an Idempotency-Key, a repeated POST creates a new booking each time — so always send one if the caller might retry.

Your organization (authenticated)

GET/ API key

Confirms your API key and returns the organization it belongs to — a handy smoke test.

curl https://ohmydesk.app/api/v1 \
  -H "Authorization: Bearer omd_live_…"

GET/availability API key

Your own organization's availability, in the same shape as the public read above.

List your bookings (authenticated)

GET/bookings API key

Lists your organization's desk and meeting-room bookings in a date window. Frozen desk days and cancelled meeting-room bookings are left out. Every row carries a type (desk or room) and the id you pass to cancel.

curl "https://ohmydesk.app/api/v1/bookings?from=2026-07-20&to=2026-08-20&type=all" \
  -H "Authorization: Bearer omd_live_…"

Query

Response200 OK (rows sorted by date; fields abbreviated below)

{
  "data": {
    "bookings": [
      {
        "type": "desk",
        "id": "720307628",
        "deskId": "room2-desk4",
        "date": "2026-07-20",
        "status": "assigned",
        "personName": "Ada Lovelace",
        "isFlex": false,
        "clientId": "1745"
      },
      {
        "type": "room",
        "id": "1ef38266-7b46-415d-b6f4-690c7673e382",
        "meetingRoomId": "c838f5dd-…",
        "date": "2026-07-20",
        "startTime": "2026-07-20T09:00:00+00:00",
        "endTime": "2026-07-20T10:00:00+00:00",
        "status": "booked",
        "bookingGroupId": null,
        "isFullDay": false
      }
    ],
    "range": { "from": "2026-07-20", "to": "2026-08-20" }
  },
  "error": null,
  "meta": { "organization_id": "…" }
}

A multi-day desk booking shows up as one row per day, each with its own id — cancelling one of those ids cancels just that day.

Cancel a booking (authenticated)

POST/bookings/{id}/cancel API key

Cancels a booking your organization owns. The id comes from list bookings: a numeric id cancels a desk booking (removed — and if it was a flex day, the member's flex allowance is refunded, shown by flexRestored); a UUID cancels a meeting-room booking (marked cancelled). Cancelling one day of a whole-day reservation cancels the whole group, and rowsCancelled reports how many rows were affected. An id that doesn't belong to your organization returns booking_not_found — never touching another space's data.

curl -X POST https://ohmydesk.app/api/v1/bookings/720307628/cancel \
  -H "Authorization: Bearer omd_live_…" \
  -H "Idempotency-Key: cancel-720307628"

Response200 OK

{
  "data": {
    "cancelled": true,
    "type": "desk",
    "id": "720307628",
    "deskId": "room2-desk4",
    "date": "2026-07-20",
    "flexRestored": false
  },
  "error": null,
  "meta": { "organization_id": "…" }
}

Re-cancelling an already-cancelled meeting room returns 200 with already: true. Send an Idempotency-Key to make a cancel safe to retry. Smart-lock access codes are not yet revoked automatically on cancel.

Edit or reschedule a booking (authenticated)

PATCH/bookings/{id} API key

Edits or reschedules a booking your organization owns — a meeting-room booking (the id is a room UUID) or a single-day desk booking (the id is a numeric desk-booking id), both from list bookings. Send only the fields you want to change — everything else is left as-is.

Meeting-room booking

To reschedule, send date and/or startMinute/endMinute (minutes from midnight, like the create endpoints); the new time is validated against the room's opening hours, slot size, and min/max length, and re-checked for conflicts (a clash returns slot_taken).

curl -X PATCH https://ohmydesk.app/api/v1/bookings/1ef38266-7b46-415d-b6f4-690c7673e382 \
  -H "Authorization: Bearer omd_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "date": "2026-07-21",
    "startMinute": 660,
    "endMinute": 780,
    "personName": "Ada Lovelace",
    "status": "confirmed"
  }'

Body — all optional; at least one required

Response200 OK

{
  "data": {
    "updated": true,
    "id": "1ef38266-7b46-415d-b6f4-690c7673e382",
    "type": "room",
    "meetingRoomId": "…",
    "date": "2026-07-21",
    "startTime": "2026-07-21T08:00:00+00:00",
    "endTime": "2026-07-21T10:00:00+00:00",
    "status": "confirmed",
    "price": 50,
    "currency": "EUR"
  },
  "error": null,
  "meta": { "organization_id": "…" }
}

Desk booking

To reschedule, send date (YYYY-MM-DD) and/or deskId (a desk id from availability). The move is applied in place — the booking keeps its id — and the target desk + date is re-checked for conflicts (a clash returns slot_taken). The id stays stable, so any smart-lock grants and invoice lines linked to it are preserved.

curl -X PATCH https://ohmydesk.app/api/v1/bookings/21808 \
  -H "Authorization: Bearer omd_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "deskId": "room2-desk4",
    "date": "2026-07-21",
    "personName": "Ada Lovelace",
    "status": "assigned"
  }'

Body — all optional; at least one required

Response200 OK

{
  "data": {
    "updated": true,
    "id": 21808,
    "type": "desk",
    "deskId": "room2-desk4",
    "date": "2026-07-21",
    "status": "assigned"
  },
  "error": null,
  "meta": { "organization_id": "…" }
}

Desk edits cover single-day visitor bookings. A desk booking that's part of a member plan (flex, monthly, ongoing…) or spans multiple days returns member_booking_edit_unsupported; a whole-day / multi-day meeting-room group returns group_edit_unsupported — cancel and re-create instead. An unknown deskId returns desk_not_found, and editing an id that isn't yours returns booking_not_found. Send an Idempotency-Key to make an edit safe to retry.

Errors

Errors use the same envelope with data: null and an error object. Date-specific errors also carry the offending date.

{
  "data": null,
  "error": {
    "code": "no_desks_available",
    "message": "No desks are available on 2026-07-20. Please adjust the dates.",
    "date": "2026-07-20"
  }
}
CodeHTTPMeaning
missing_visitor_name400No visitorName was supplied.
missing_visitor_email400A paid whole-day or office checkout needs a visitorEmail (email on office requests).
company_required400An office request needs a companyName.
no_dates400The dates array was empty.
invalid_date400A date was not a valid YYYY-MM-DD.
date_out_of_range400A date is in the past or beyond the space's booking window.
invalid_return_url400returnUrl was set but is not an absolute http(s) URL.
invalid_range400A from/to window is backwards, or a booking range ends before it starts.
range_too_long400A bookings list window exceeds 366 days (or a whole-day range exceeds 60).
invalid_id400A cancel/edit id is neither a meeting-room UUID nor a numeric desk-booking id.
invalid_type400The bookings list type filter was not desk, room, or all.
no_changes400A PATCH sent no editable fields.
past_date400A whole-day or desk reschedule targeted a date before today.
invalid_body400The request body was not valid JSON.
org_not_found_or_disabled404Unknown space, or public booking is turned off.
booking_not_found404No booking with that id belongs to your organization (also returned when editing/cancelling another org's booking).
desk_not_found404A desk reschedule named a deskId that does not exist in your organization.
no_desks_available409Every desk is taken on one of the requested dates.
slot_taken409The meeting-room time slot, or the target desk + date, is already taken.
booking_cancelled409A PATCH targeted an already-cancelled booking.
group_edit_unsupported409A PATCH targeted a whole-day / multi-day group booking — cancel and re-create instead.
member_booking_edit_unsupported409A PATCH targeted a desk booking that's part of a member plan (flex, monthly, ongoing…) or spans multiple days — not editable through the API yet.
office_taken409The office already has an active agreement.
processing409A booking with this Idempotency-Key is still being processed.
unauthorized401No API key was sent on an authenticated endpoint.
invalid_key401The API key is unknown or has been revoked.
checkout_failed502Could not start Stripe checkout for a paid resource — safe to retry.
payment_not_configured503Card payment is on for the resource, but the space hasn't finished its Stripe setup.

What's next

Availability, desk, meeting-room, whole-day and office writes — including paid checkout for whole-day rooms and offices — plus listing, cancelling, and editing/rescheduling desk and meeting-room bookings with an API key are live today. Member-plan and multi-day desk edits, member and office-agreement endpoints, and more authenticated writes are on the way. Questions or early access? Email hello@ohmydesk.app.