Bookings

List rooms and equipment, take reservations, and manage bookables, closures and requests.

In Loupe, anything people can book (a group room, a quiet seat, a laptop) is a bookable. A booking is a reservation of a bookable for a time range.

List bookables

Shell
curl "https://api.loupe.ch/api/public/bookables?organization.slug=riveville&bookableType=room" \  -H "Accept: application/json"
JSON
{  "id": "0199…",  "title": "Group room 2.04",  "slug": "group-room-2-04",  "bookableType": "room",  "calendarType": "slot",  "capacity": 6,  "slotSize": 60,  "minSlotPerReservation": 1,  "maxSlotPerReservation": 3,  "maxAdvanceDays": 14,  "equipments": ["projector", "whiteboard"],  "autoValidate": false,  "membersOnly": false,  "locationName": "Level 2"}

slotSize is in minutes; a reservation spans between minSlotPerReservation and maxSlotPerReservation slots. A bookable with calendarType: "day" is booked by whole days instead. autoValidate tells you whether a reservation is confirmed immediately or waits for the team. equipments uses a fixed list: table, paperboard, projector, whiteboard, wheelchair_access, power_outlet.

One bookable by slug: GET /api/public/bookables/item?organization.slug=riveville&slug=group-room-2-04.

Find what is free

Ask for the bookables available on a day, or over a time range:

Shell
curl -X POST https://api.loupe.ch/api/public/bookables/available \  -H "Content-Type: application/json" \  -d '{ "organization": "riveville", "date": "2026-10-17", "bookableType": "room" }'

For a range, send from and to instead of date. Add equipment to only get bookables that have it. Closures (holidays, maintenance) of one bookable are listed by GET /api/public/bookables/{id}/closures?from=…&to=….

Create a reservation

Shell
curl -X POST https://api.loupe.ch/api/public/bookables/0199…/reservation \  -H "Content-Type: application/json" \  -d '{    "startAt": "2026-10-17 14:00:00",    "endAt": "2026-10-17 16:00:00",    "email": "camille@example.org",    "firstname": "Camille",    "lastname": "Martin",    "guests": 3  }'

Times are local, in the library's time zone, and must match the bookable's slots. The response tells you whether the reservation is confirmed (validated: true) or pending, and carries a token and a reservationLink that the person also receives by e-mail.

Only confirmed reservations hold a slot: a pending request does not block anyone else until the team confirms it.

When the API refuses a reservation it answers 400 with a code in detail and context.reason:

CodeMeaning
slot_already_reservedSomeone already holds that time
slot_closed, outside_opening_hoursThe library is closed then
guests_exceed_capacityToo many people for the room
duration_too_short, duration_exceededFewer or more slots than allowed
lead_time_not_reachedToo close to now; context.availableFrom says when booking opens
beyond_booking_horizonToo far ahead; see context.maxAdvanceDays
below_min_participantsThe group is smaller than required
too_many_active_reservationsThe person already has the maximum number of upcoming reservations
members_onlyReserved for library members, see context.reason

The token lets the person, or your site on their behalf, follow the reservation without an account. Treat it like a password.

Shell
curl "https://api.loupe.ch/api/public/reservations/view?token=…"curl -X POST "https://api.loupe.ch/api/public/reservations/cancel?token=…"

Cancelling answers 204, also when the reservation was already cancelled, and e-mails the person.

Check-in. When a bookable has enableCheckIn, people confirm they came from the room itself: POST /api/public/bookables/{id}/checkin with their email and the device's latitude and longitude. The device must be near the library, and the check-in window opens shortly before the reservation (15 minutes by default) and closes shortly after it starts (10 minutes by default). A reservation not checked in by then is cancelled automatically. Refusals carry out_of_radius, reservation_expired or no_open_reservation.

Manage bookables

These endpoints need an API key whose owner holds the matching permission: bookables.create, bookables.update_own or bookables.update_any, and so on. "Own" covers the bookables that person created.

Create a bookable. It starts unpublished unless you send isPublished: true, which needs the publish permission:

Shell
curl -X POST "https://api.loupe.ch/api/manager/bookables?organization.slug=riveville" \  -H "X-Api-Key: $LOUPE_API_KEY" \  -H "Content-Type: application/json" \  -d '{    "title": "Group room 2.04",    "slug": "group-room-2-04",    "bookableType": "room",    "calendarType": "slot",    "capacity": 6,    "slotSize": 60,    "minSlotPerReservation": 1,    "maxSlotPerReservation": 3,    "leadTimeAmount": 2,    "leadTimeUnit": "hours",    "maxAdvanceDays": 14,    "autoValidate": false,    "equipments": ["whiteboard"]  }'

title and slug are required; the slug must be unique within the organization. slotSize is 15, 30 or 60. The booking rules are all optional:

FieldRule
leadTimeAmount + leadTimeUnit (hours or days)How long in advance a reservation must be made
maxAdvanceDaysHow far ahead people can book, up to 730 days
minParticipantsSmallest group, the person booking included
membersOnlyOnly library members can book

By default a bookable follows the library's opening hours. Override them per weekday in settings.openingHours, for example "monday": { "inherit": false, "ranges": [{ "open": "09:00", "close": "12:00" }] }.

Update or publish with PATCH /api/manager/bookables/{id} and Content-Type: application/merge-patch+json; only the fields you send change. Changing isPublished needs bookables.publish_own or bookables.publish_any. Duplicate with POST /api/manager/bookables/{id}/duplicate: the copy is unpublished, keeps the rules, and has no closures or reservations. Delete with DELETE /api/manager/bookables/{id}: the bookable goes to the Loupe trash for 30 days.

Close slots

Close a bookable for maintenance or an event:

Shell
curl -X POST https://api.loupe.ch/api/manager/bookables/0199…/closures/batch \  -H "X-Api-Key: $LOUPE_API_KEY" \  -H "Content-Type: application/json" \  -d '{    "create": [{ "startAt": "2026-12-24 00:00:00", "endAt": "2026-12-27 00:00:00" }],    "delete": ["0199…"]  }'

delete reopens closures by their id, as returned by GET /api/manager/bookables/{id}/closures?from=…&to=…. A closure only blocks new reservations: existing ones are kept, so cancel them separately if needed.

Manage reservations

MethodPathUse
GET/api/manager/reservations?organization=riveville&period=upcoming&page=1Reservations, 25 per page, as { items, totalItems, page, pageSize }
GET/api/manager/reservations/{id}?organization=rivevilleOne reservation, with its contact details and participants
POST/api/manager/reservations/{id}/validateConfirm a pending reservation, optionally moving it with newBookable; the person is e-mailed
POST/api/manager/reservations/{id}/cancelCancel with a required {"reason": "…"}; the person is e-mailed
GET/api/manager/statistics/bookables?from=…&to=…Booking statistics for a period

They need reservations.own (reservations of your own bookables) or reservations.any. Validating answers 400 slot_already_reserved when another confirmed reservation overlaps. Use these two actions rather than patching or deleting a reservation directly: only they check overlaps and notify the person.