API Reference¶
RoomVox has two types of API:
- Internal API (
/api/...) — Used by the admin interface. Requires a Nextcloud session (cookie-based authentication). - Public API v1 (
/api/v1/...) — For external integrations. Requires a Bearer token (see API Tokens).
All endpoints are relative to /apps/roomvox/ (or /index.php/apps/roomvox/ for POST/PUT/DELETE requests).
Public API v1¶
The public API is designed for external systems: room displays, kiosks, digital signage, Power Automate, Zapier, and custom applications. All endpoints require a Bearer token.
Authentication¶
All v1 endpoints require an API token sent as a Bearer token:
Tokens are managed in the RoomVox admin panel under Settings > API Tokens.
Scopes¶
| Scope | Level | Access |
|---|---|---|
read |
1 | View rooms, availability, bookings, iCal feed |
book |
2 | Everything in read + create and cancel bookings |
admin |
3 | Everything in book + statistics |
Scopes are hierarchical: a book token can do everything a read token can.
Tokens can optionally be restricted to specific rooms. If no room restriction is set, the token has access to all rooms.
Room Status¶
Get Current Room Status¶
Scope: read
Returns the real-time status of a room: free, busy, or unavailable (outside availability rules).
Response:
{
"room": {
"id": "meeting-room-1",
"name": "Meeting Room 1",
"email": "meeting-room-1@roomvox.local",
"capacity": 12,
"roomNumber": "2.17",
"roomType": "meeting-room",
"facilities": ["projector", "whiteboard"],
"location": "Building A, Heidelberglaan 8, 3584 CS Utrecht",
"autoAccept": true,
"active": true
},
"status": "busy",
"currentBooking": {
"title": "Team standup",
"organizer": "Jan de Vries",
"start": "2026-02-15T09:00:00+01:00",
"end": "2026-02-15T09:30:00+01:00",
"minutesRemaining": 12
},
"nextBooking": {
"title": "Sprint planning",
"organizer": "Maria Schmidt",
"start": "2026-02-15T10:00:00+01:00",
"end": "2026-02-15T11:00:00+01:00"
},
"freeUntil": null,
"todayBookings": [
{
"title": "Team standup",
"start": "2026-02-15T09:00:00+01:00",
"end": "2026-02-15T09:30:00+01:00",
"status": "accepted"
}
]
}
Status values:
| Status | Meaning |
|---|---|
free |
Room is available now |
busy |
Room is currently occupied |
unavailable |
Outside configured availability hours |
When status is free and there is a next booking, freeUntil contains the start time of the next booking.
Get Room Availability¶
Scope: read
Returns time slots for a given date showing which periods are free or busy.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
date |
string | today | Date in YYYY-MM-DD format |
from |
string | — | Custom range start (ISO 8601), overrides date |
to |
string | — | Custom range end (ISO 8601), overrides date |
Note: When using
from/to, the date range must not exceed 365 days. Invalid dates return a 400 error.
Response:
{
"room": { "id": "meeting-room-1", "name": "Meeting Room 1" },
"date": "2026-02-15",
"availabilityRules": {
"start": "08:00",
"end": "18:00",
"days": ["mon", "tue", "wed", "thu", "fri"]
},
"slots": [
{ "start": "08:00", "end": "09:00", "status": "free" },
{ "start": "09:00", "end": "09:30", "status": "busy", "title": "Team standup" },
{ "start": "09:30", "end": "10:00", "status": "free" },
{ "start": "10:00", "end": "11:00", "status": "busy", "title": "Sprint planning" },
{ "start": "11:00", "end": "18:00", "status": "free" }
]
}
Rooms¶
List Rooms¶
Scope: read
Returns all rooms accessible to the token. If the token has room restrictions, only those rooms are returned.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
active |
string | Filter by active status (true or false) |
type |
string | Filter by room type (e.g., meeting-room) |
capacity_min |
int | Minimum capacity |
Response:
[
{
"id": "meeting-room-1",
"name": "Meeting Room 1",
"email": "meeting-room-1@roomvox.local",
"capacity": 12,
"roomNumber": "2.17",
"roomType": "meeting-room",
"facilities": ["projector", "whiteboard"],
"description": "Large meeting room on 2nd floor",
"responsibleContact": "Anne Janssen (anne@voxcloud.nl)",
"location": "Building A, Heidelberglaan 8, 3584 CS Utrecht",
"autoAccept": true,
"active": true
}
]
The responsibleContact field (since 1.1.0) is a free-text string admins use to tell viewers who to approach when they can't book the room themselves. Empty string when not configured.
Get Room Details¶
Scope: read
Response: Single room object (same structure as list items).
Bookings¶
List Bookings for a Room¶
Scope: read
Query parameters:
| Parameter | Type | Description |
|---|---|---|
from |
string | Start date (ISO 8601) |
to |
string | End date (ISO 8601) |
status |
string | Filter: accepted, pending, or declined |
Note: The date range must not exceed 365 days. Invalid dates return a 400 error.
Response:
[
{
"uid": "abc123-def456",
"title": "Team Meeting",
"start": "2026-03-01T10:00:00+01:00",
"end": "2026-03-01T11:00:00+01:00",
"organizer": "Alice",
"status": "accepted",
"room": { "id": "meeting-room-1", "name": "Meeting Room 1" }
}
]
Create Booking¶
Scope: book
Request body:
{
"title": "Team meeting",
"start": "2026-02-15T14:00:00+01:00",
"end": "2026-02-15T15:00:00+01:00",
"organizer": "j.devries@company.com",
"description": "Weekly team sync"
}
title, start, and end are required. organizer and description are optional.
Response (201):
{
"uid": "abc-123-def",
"title": "Team meeting",
"start": "2026-02-15T14:00:00+01:00",
"end": "2026-02-15T15:00:00+01:00",
"status": "accepted",
"room": { "id": "meeting-room-1", "name": "Meeting Room 1" }
}
The status depends on the room's auto-accept setting: accepted if auto-accept is on, pending if manual approval is required.
Error responses:
| Status | Body | Meaning |
|---|---|---|
| 400 | {"error": "title, start, and end are required"} |
Missing fields |
| 400 | {"error": "Invalid date format for start or end"} |
Unparseable dates |
| 400 | {"error": "End time must be after start time"} |
End before or equal to start |
| 409 | {"error": "Room is already booked during this time"} |
Scheduling conflict |
| 422 | {"error": "Booking is outside available hours"} |
Outside availability rules |
| 422 | {"error": "Booking exceeds maximum booking horizon"} |
Too far in advance |
Cancel Booking¶
Scope: book
Response:
iCalendar Feed¶
Get Room Calendar Feed¶
Scope: read
Returns an iCalendar (.ics) feed with all accepted bookings for the room. Compatible with any calendar application, room display, or digital signage system.
Recurring events: master VEVENTs are passed through with RRULE, EXDATE, and any RECURRENCE-ID overrides intact. Clients expand the recurrences themselves — RoomVox does not pre-expand the series, which would produce duplicate UIDs and cause RFC 5545–compliant clients to deduplicate down to a single event.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
from |
string | unbounded | Optional start of date range (ISO 8601). Filters non-recurring events only — events with an RRULE always pass through |
to |
string | unbounded | Optional end of date range (ISO 8601). Filters non-recurring events only |
Response:
Content-Type: text/calendar; charset=utf-8
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//RoomVox//Nextcloud//EN
X-WR-CALNAME:Meeting Room 1
BEGIN:VEVENT
UID:abc-123-def
DTSTART;TZID=Europe/Amsterdam:20260215T130000
DTEND;TZID=Europe/Amsterdam:20260215T140000
SUMMARY:Team standup
RRULE:FREQ=WEEKLY;BYDAY=MO
ORGANIZER;CN=Jan de Vries:mailto:j.devries@company.com
LOCATION:Building A, Heidelberglaan 8, 3584 CS Utrecht
STATUS:CONFIRMED
END:VEVENT
END:VCALENDAR
Use cases: - Room displays (SyncSign, Joan, Crestron) can subscribe to this feed - Calendar apps (Google Calendar, Apple Calendar, Thunderbird) can add as a read-only subscription - Digital signage can poll this URL periodically
Statistics¶
Get Usage Statistics¶
Scope: admin
Returns room and booking statistics with per-room utilization data.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
from |
string | 30 days ago | Start date (YYYY-MM-DD) |
to |
string | today | End date (YYYY-MM-DD) |
room |
string | — | Filter by room ID |
Note: The date range must not exceed 365 days. If the token is restricted to specific rooms, only those rooms are included in the statistics.
Response:
{
"period": { "from": "2026-02-01", "to": "2026-02-15" },
"rooms": {
"total": 15,
"active": 12,
"byType": { "meeting-room": 8, "studio": 2, "lecture-hall": 2 }
},
"bookings": {
"total": 342,
"accepted": 298,
"declined": 22,
"pending": 12,
"cancelled": 10
},
"utilization": [
{
"roomId": "meeting-room-1",
"roomName": "Meeting Room 1",
"totalHoursBooked": 86.5,
"totalHoursAvailable": 160,
"utilizationPercent": 54.1,
"bookingCount": 48
}
]
}
Public API Error Responses¶
| Status | Meaning |
|---|---|
| 400 | Invalid input (bad dates, missing fields, end before start, date range > 365 days) |
| 401 | Missing or invalid Bearer token |
| 403 | Token scope insufficient, or token has no access to this room |
| 404 | Room or booking not found |
| 409 | Scheduling conflict |
| 422 | Booking validation failed (outside hours, beyond horizon) |
| 500 | Server error |
Error format:
Quick Start Example¶
# Create a token in the admin UI, then:
# List all rooms
curl -H "Authorization: Bearer rvx_your_token_here" \
https://cloud.example.com/apps/roomvox/api/v1/rooms
# Check if a room is free
curl -H "Authorization: Bearer rvx_your_token_here" \
https://cloud.example.com/apps/roomvox/api/v1/rooms/meeting-room-1/status
# Check availability for tomorrow
curl -H "Authorization: Bearer rvx_your_token_here" \
"https://cloud.example.com/apps/roomvox/api/v1/rooms/meeting-room-1/availability?date=2026-02-16"
# Create a booking (note: use /index.php/ prefix for POST)
curl -X POST \
-H "Authorization: Bearer rvx_your_token_here" \
-H "Content-Type: application/json" \
-d '{"title":"Team sync","start":"2026-02-16T10:00:00+01:00","end":"2026-02-16T11:00:00+01:00"}' \
https://cloud.example.com/index.php/apps/roomvox/api/v1/rooms/meeting-room-1/bookings
# Subscribe to iCal feed
curl -H "Authorization: Bearer rvx_your_token_here" \
https://cloud.example.com/apps/roomvox/api/v1/rooms/meeting-room-1/calendar.ics
API Tokens¶
Token management endpoints for administrators. These use Nextcloud session authentication (not Bearer tokens).
List Tokens¶
Required: Admin
Response:
[
{
"id": "tok_abc123",
"name": "Lobby Display",
"scope": "read",
"roomIds": [],
"createdAt": "2026-02-15T10:00:00+01:00",
"lastUsedAt": "2026-02-15T14:30:00+01:00",
"expiresAt": null
}
]
Create Token¶
Required: Admin
Request body:
{
"name": "Lobby Display",
"scope": "read",
"roomIds": ["meeting-room-1", "meeting-room-2"],
"expiresAt": "2026-12-31T23:59:59+01:00"
}
name is required. scope defaults to read. roomIds and expiresAt are optional.
Response (201):
{
"id": "tok_abc123",
"name": "Lobby Display",
"token": "rvx_aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789ab",
"scope": "read",
"roomIds": ["meeting-room-1", "meeting-room-2"],
"createdAt": "2026-02-15T10:00:00+01:00",
"lastUsedAt": null,
"expiresAt": "2026-12-31T23:59:59+01:00"
}
The
tokenfield is only returned on creation. Store it immediately — it cannot be retrieved later.
Delete Token¶
Required: Admin
Response:
Import/Export¶
Bulk room management via CSV files. All endpoints require admin access.
Export Rooms¶
Required: Admin
Downloads all rooms as a CSV file with the following columns:
| Column | Description |
|---|---|
name |
Room name |
email |
Room email address |
capacity |
Number of seats |
roomNumber |
Room/floor number |
roomType |
Room type ID |
building |
Building name |
street |
Street address |
postalCode |
Postal/ZIP code |
city |
City |
facilities |
Comma-separated facility list |
description |
Room description |
autoAccept |
true or false |
active |
true or false |
Download Sample CSV¶
Required: Admin
Downloads a sample CSV file with headers and one example row. Useful as a template for creating import files.
Import Preview¶
Required: Admin
Upload a CSV file to preview what will happen before importing. Supports both RoomVox and MS365/Exchange formats (auto-detected). Maximum file size: 5 MB.
Request: multipart/form-data with a file field.
Response:
{
"columns": ["name", "email", "capacity", "roomNumber", "..."],
"rows": [
{
"line": 2,
"data": {
"name": "Meeting Room 1",
"email": "room1@company.com",
"capacity": "12",
"roomType": "meeting-room"
},
"action": "create",
"matchedId": null,
"matchedName": null,
"errors": []
},
{
"line": 3,
"data": { "name": "Existing Room", "..." : "..." },
"action": "update",
"matchedId": "existing-room-id",
"matchedName": "Existing Room",
"errors": []
}
],
"detected_format": "roomvox"
}
Actions: create (new room) or update (matches existing room by email or name).
Import Rooms¶
Required: Admin
Request: multipart/form-data with file and mode fields.
| Parameter | Type | Default | Description |
|---|---|---|---|
file |
file | — | CSV file (required, max 5 MB) |
mode |
string | create |
create (skip existing) or update (create + update existing) |
Response:
{
"created": 5,
"updated": 2,
"skipped": 1,
"errors": [
{
"line": 4,
"name": "Bad Room",
"errors": ["Room name is required"]
}
]
}
Supported CSV Formats¶
RoomVox format — Standard CSV with the column names listed above.
MS365/Exchange format — Exported via Get-EXOMailbox | Get-Place | Export-Csv. Column mapping:
| MS365 Column | RoomVox Field |
|---|---|
DisplayName |
name |
PrimarySmtpAddress / EmailAddress |
|
Capacity / ResourceCapacity |
capacity |
Floor / FloorLabel |
roomNumber |
Building |
building |
City |
city |
Tags |
facilities |
IsWheelchairAccessible |
wheelchair facility |
The format is automatically detected based on column names.
Internal API¶
The internal API is used by the RoomVox admin interface. It requires Nextcloud session authentication (cookies + CSRF token).
Rooms¶
List All Rooms¶
Returns all rooms visible to the current user, with permission flags.
Response:
{
"rooms": [
{
"id": "meeting-room-1",
"name": "Meeting Room 1",
"roomNumber": "2.17",
"address": "Main Building, Kerkstraat 10, Amsterdam",
"roomType": "meeting-room",
"capacity": 10,
"email": "meeting-room-1@roomvox.local",
"description": "Corner room with projector",
"facilities": ["projector", "whiteboard"],
"autoAccept": true,
"active": true,
"groupId": "building-a",
"canBook": true,
"canManage": false
}
]
}
Note: SMTP passwords are always masked as
"***"in responses.
Create Room¶
Required: Admin
Request body:
{
"name": "Meeting Room 1",
"roomNumber": "2.17",
"capacity": 10,
"roomType": "meeting-room",
"address": "Main Building, Kerkstraat 10, Amsterdam",
"description": "Corner room with projector",
"responsibleContact": "Anne Janssen (anne@voxcloud.nl)",
"facilities": ["projector", "whiteboard", "videoconf"],
"autoAccept": true,
"groupId": "building-a",
"email": "room1@company.com",
"availabilityRules": {
"enabled": true,
"rules": [
{ "days": [1,2,3,4,5], "startTime": "08:00", "endTime": "18:00" }
]
},
"maxBookingHorizon": 90,
"smtpConfig": {
"host": "smtp.company.com",
"port": 587,
"username": "room1@company.com",
"password": "secret",
"encryption": "tls"
}
}
Only name is required. All other fields are optional. The address field uses the 4-part comma-separated format (Building, Street, Postal code, City) — empty parts are preserved so partial addresses round-trip correctly. The responsibleContact field (since 1.1.0, max 255 chars) is visible to every user with view-permission in Personal Settings → My Rooms.
Response: The created room object.
Get Room¶
Required: Manager or Admin
Response: Single room object.
Update Room¶
Required: Manager or Admin
Request body: Same fields as create (all optional, only provided fields are updated).
Response: Updated room object.
Delete Room¶
Required: Admin
Deletes the room, its calendar, service account, and permissions.
Response:
Bookings¶
List All Bookings¶
Returns bookings across all rooms visible to the current user.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
room |
string | Filter by room ID |
status |
string | all, pending, accepted, declined |
from |
string | Start date (ISO 8601) |
to |
string | End date (ISO 8601) |
scope |
string | view (default) or manage. With scope=manage only rooms the user can manage are included — used by the Manager Bookings tab in Personal Settings (since 1.1.0). Admins always see every room regardless of scope |
Response:
{
"bookings": [
{
"uid": "abc123-def456",
"summary": "Team Meeting",
"dtstart": "2026-03-01T10:00:00Z",
"dtend": "2026-03-01T11:00:00Z",
"organizer": "alice@company.com",
"organizerName": "Alice",
"partstat": "ACCEPTED",
"roomId": "meeting-room-1",
"roomName": "Meeting Room 1"
}
],
"stats": {
"total": 42,
"accepted": 35,
"pending": 5,
"declined": 2
}
}
List Room Bookings¶
Required: Manager or Admin
Query parameters:
| Parameter | Type | Description |
|---|---|---|
from |
string | Start date (ISO 8601) |
to |
string | End date (ISO 8601) |
Response:
{
"bookings": [
{
"uid": "abc123-def456",
"summary": "Team Meeting",
"dtstart": "2026-03-01T10:00:00Z",
"dtend": "2026-03-01T11:00:00Z",
"organizer": "alice@company.com",
"organizerName": "Alice",
"partstat": "ACCEPTED",
"status": "CONFIRMED"
}
]
}
Create Booking¶
Required: Booker, Manager, or Admin
Request body:
{
"summary": "Team Meeting",
"start": "2026-03-01T10:00:00Z",
"end": "2026-03-01T11:00:00Z",
"description": "Weekly sync"
}
summary, start, and end are required.
Response:
Error (409 Conflict):
Update Booking¶
Required: Organizer, Manager, or Admin
Request body:
If roomId is provided and differs from the current room, the booking is moved (deleted from old room, created in new room with a new UID).
Response:
Respond to Booking¶
Required: Manager or Admin
Request body:
action must be accept or decline.
Response:
Delete Booking¶
Required: Organizer, Manager, or Admin
Side effects (since 1.1.0):
When called by a manager or admin on an already-accepted booking, the booking is fully cancelled, not just removed from the room calendar:
- The booking is deleted from the room calendar (and Exchange, if configured)
- The room attendee is removed from the booker's own calendar event and
LOCATIONis cleared, so the slot frees up in the Room Finder - A
Booking cancelledemail is sent to the booker explaining the booking was cancelled by a room manager
Steps 2 and 3 are non-blocking — if the organizer's calendar cleanup or the mail fails, the API still returns 200 OK and the failure is logged.
Response:
Permissions¶
Get Room Permissions¶
Required: Manager or Admin
Response:
{
"viewers": [
{ "type": "group", "id": "staff" }
],
"bookers": [
{ "type": "user", "id": "alice" },
{ "type": "group", "id": "developers" }
],
"managers": [
{ "type": "user", "id": "bob" }
]
}
Set Room Permissions¶
Required: Manager or Admin
Request body: Same structure as the GET response.
Response:
Room Groups¶
List Room Groups¶
Required: Admin
Response:
{
"groups": [
{
"id": "building-a",
"name": "Building A",
"description": "Main office building",
"createdAt": "2026-01-15T10:00:00Z"
}
]
}
Create Room Group¶
Required: Admin
Request body:
name is required.
Response: Created group object.
Get Room Group¶
Required: Admin
Response: Single group object.
Update Room Group¶
Required: Admin
Request body: name and/or description.
Response: Updated group object.
Delete Room Group¶
Required: Admin
Fails with 409 Conflict if any rooms are still assigned to the group.
Response:
Get Group Permissions¶
Required: Admin
Response: Same structure as room permissions.
Set Group Permissions¶
Required: Admin
Request body: Same structure as room permissions.
Response:
Settings¶
Get Settings¶
Required: Admin
Response:
{
"defaultAutoAccept": true,
"emailEnabled": true,
"roomTypes": [
{ "id": "meeting-room", "label": "Meeting Room" },
{ "id": "studio", "label": "Studio" },
{ "id": "lecture-hall", "label": "Lecture Hall" }
]
}
Save Settings¶
Required: Admin
Request body: Same structure as GET response (all fields optional).
Response:
Sharees¶
Search Users and Groups¶
Search for Nextcloud users and groups to add in the permission editor.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
search |
string | Search query (name or group name) |
Response:
{
"users": [
{ "id": "alice", "displayName": "Alice Smith" }
],
"groups": [
{ "id": "developers", "displayName": "Developers" }
]
}
License & Telemetry¶
Get License Stats¶
Required: Admin
Returns license status, usage statistics, and telemetry state.
Save License Key¶
Required: Admin
Body:
Validate License¶
Required: Admin
Validates the stored license key with the license server.
Send Telemetry Report¶
Required: Admin
Immediately sends anonymous usage statistics to the telemetry server.
Response:
On failure, includes the specific error:
Debug¶
Debug Room Registration¶
Required: Admin
Returns internal details about room backend registration, room principals, and CalDAV calendars. Useful for troubleshooting.
Error Responses¶
Internal API¶
| Status | Meaning |
|---|---|
| 401 | Not authenticated |
| 403 | Insufficient permissions |
| 404 | Room or booking not found |
| 409 | Conflict (scheduling conflict or group has rooms) |
| 500 | Server error |
Error format:
Public API v1¶
| Status | Meaning |
|---|---|
| 401 | Missing or invalid Bearer token |
| 403 | Insufficient scope or no room access |
| 404 | Room or booking not found |
| 409 | Scheduling conflict |
| 422 | Validation error (outside hours, beyond horizon) |
| 500 | Server error |
Error format: