> For the complete documentation index, see [llms.txt](https://mobile-calendar.gitbook.io/v1/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mobile-calendar.gitbook.io/v1/rest-api/rooms.md).

# Rooms

Create, read, update, and delete operations for rooms.

## Get room photos

> Returns the room's photo gallery ordered by \`order\` (1 = cover photo). A room that belongs to a room type has NO own gallery — the type's photos are returned with \`meta.inheritedFromRoomTypeId\` set; manage them via /room-types/{roomTypeId}/photos. Required scope: \`roomPhotos.read\`.

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"rooms","description":"Create, read, update, and delete operations for rooms."}],"servers":[{"url":"https://api.mobile-calendar.com/v1/public","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"Pass the token in header: Authorization: Bearer <JWT>","bearerFormat":"JWT","scheme":"bearer"}},"schemas":{"RoomPhotosResponse":{"required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RoomPhotoEntry"}},"meta":{"type":"object","allOf":[{"$ref":"#/components/schemas/MetaWithRuid"},{"properties":{"roomId":{"description":"Present for room endpoints.","type":"integer"},"roomTypeId":{"description":"Present for room-type endpoints.","type":"integer"},"inheritedFromRoomTypeId":{"description":"Room endpoints only: set when the room belongs to a room type — the listed photos are the TYPE's gallery (manage them via /room-types/{id}/photos).","type":"integer","nullable":true},"totalPhotos":{"type":"integer"},"maxPhotos":{"type":"integer"}},"type":"object"}]}},"type":"object"},"RoomPhotoEntry":{"description":"One photo of a room / room type gallery (shown in the panel, the booking engine and used when exporting listings to OTAs). Photos are stored resized to max 1200 px.","required":["photoId","order","url","fileName","addDate"],"properties":{"photoId":{"type":"integer"},"order":{"description":"Position in the gallery (1 = first/cover photo); always contiguous 1..n.","type":"integer","minimum":1},"url":{"description":"Public URL of the image.","type":"string"},"fileName":{"type":"string"},"addDate":{"type":"string","format":"date-time","nullable":true}},"type":"object"},"MetaWithRuid":{"description":"Metadata with request tracking.","required":["ruid"],"properties":{"ruid":{"description":"Request Unique ID","type":"string","format":"uuid"}},"type":"object"},"ApiProblem401":{"description":"Missing authentication or invalid credentials.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"401 Unauthorized","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblemBase":{"description":"RFC 7807 Problem Details (base schema).","required":["type","title","status","detail","instance"],"properties":{"type":{"description":"RFC 7807 base — DO NOT use directly in endpoints.\nIn endpoints reference ApiProblem400/401/403/409/422/429/500.","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"},"ApiProblem403":{"description":"Authenticated user but no access.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"403 Forbidden","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem404":{"description":"Resource specified in the request was not found.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"404 Not Found","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]}}},"paths":{"/rooms/{roomId}/photos":{"get":{"tags":["rooms"],"summary":"Get room photos","description":"Returns the room's photo gallery ordered by `order` (1 = cover photo). A room that belongs to a room type has NO own gallery — the type's photos are returned with `meta.inheritedFromRoomTypeId` set; manage them via /room-types/{roomTypeId}/photos. Required scope: `roomPhotos.read`.","operationId":"getRoomPhotos","parameters":[{"name":"roomId","in":"path","description":"Room ID (GET /rooms)","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Photos of the room","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomPhotosResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem401"}}}},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem403"}}}},"404":{"description":"Room not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem404"}}}}}}}}}
```

## Upload room photos (base64)

> Appends 1–3 photos to the room's gallery (see RoomPhotoUploadRequest: base64, max 7 MB each, JPEG/PNG/GIF/WebP, gallery max 8). Returns the created photos with their URLs. For a room that belongs to a room type this returns 422 — upload to /room-types/{roomTypeId}/photos instead (rooms of a type share the type's gallery). Required scope: \`roomPhotos.create\`.

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"rooms","description":"Create, read, update, and delete operations for rooms."}],"servers":[{"url":"https://api.mobile-calendar.com/v1/public","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"Pass the token in header: Authorization: Bearer <JWT>","bearerFormat":"JWT","scheme":"bearer"}},"schemas":{"RoomPhotoUploadRequest":{"description":"Upload of 1–3 images per request as base64 (plain base64 or a data URI like `data:image/jpeg;base64,…`). Each image: max 7 MB decoded, format JPEG/PNG/GIF/WebP, verified to be a real image and re-encoded server-side (resized to max 1200 px). The gallery holds at most 8 photos — the upload is rejected (422) when it would exceed that. New photos are appended at the end of the gallery.","required":["photos"],"properties":{"photos":{"type":"array","items":{"required":["data"],"properties":{"data":{"description":"Base64-encoded image (data URI accepted).","type":"string","format":"byte"}},"type":"object"},"maxItems":3,"minItems":1}},"type":"object"},"RoomPhotosResponse":{"required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RoomPhotoEntry"}},"meta":{"type":"object","allOf":[{"$ref":"#/components/schemas/MetaWithRuid"},{"properties":{"roomId":{"description":"Present for room endpoints.","type":"integer"},"roomTypeId":{"description":"Present for room-type endpoints.","type":"integer"},"inheritedFromRoomTypeId":{"description":"Room endpoints only: set when the room belongs to a room type — the listed photos are the TYPE's gallery (manage them via /room-types/{id}/photos).","type":"integer","nullable":true},"totalPhotos":{"type":"integer"},"maxPhotos":{"type":"integer"}},"type":"object"}]}},"type":"object"},"RoomPhotoEntry":{"description":"One photo of a room / room type gallery (shown in the panel, the booking engine and used when exporting listings to OTAs). Photos are stored resized to max 1200 px.","required":["photoId","order","url","fileName","addDate"],"properties":{"photoId":{"type":"integer"},"order":{"description":"Position in the gallery (1 = first/cover photo); always contiguous 1..n.","type":"integer","minimum":1},"url":{"description":"Public URL of the image.","type":"string"},"fileName":{"type":"string"},"addDate":{"type":"string","format":"date-time","nullable":true}},"type":"object"},"MetaWithRuid":{"description":"Metadata with request tracking.","required":["ruid"],"properties":{"ruid":{"description":"Request Unique ID","type":"string","format":"uuid"}},"type":"object"},"ApiProblem400":{"description":"Request syntax/format error.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"400 Bad Request","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblemBase":{"description":"RFC 7807 Problem Details (base schema).","required":["type","title","status","detail","instance"],"properties":{"type":{"description":"RFC 7807 base — DO NOT use directly in endpoints.\nIn endpoints reference ApiProblem400/401/403/409/422/429/500.","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"},"ApiProblem401":{"description":"Missing authentication or invalid credentials.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"401 Unauthorized","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem403":{"description":"Authenticated user but no access.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"403 Forbidden","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem404":{"description":"Resource specified in the request was not found.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"404 Not Found","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem422":{"description":"Input data validation error.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"422 Unprocessable Entity (validation)","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"},"errors":{"description":"Map: field name → error message(s)","type":"object","additionalProperties":{"$ref":"#/components/schemas/ErrorValue"}}},"type":"object"}]},"ErrorValue":{"description":"Single error message or list of messages.","oneOf":[{"description":"Error value in error map can be:\n- single string, or\n- array of strings (multiple messages for one field).","type":"string"},{"type":"array","items":{"type":"string"}}]},"ApiProblem502":{"description":"The external portal (OTA) did not accept the change; nothing was saved locally for the failed units.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"title":{"description":"502 Bad Gateway (OTA portal rejected the request)","type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"channel":{"type":"string"},"results":{"description":"Outcome per unit — `ok: true` entries WERE applied (partial success is possible when several units are sent).","type":"array","items":{"$ref":"#/components/schemas/ChannelPricingResult"}}},"type":"object"}]},"ChannelPricingResult":{"required":["unitId","ratePlanId","ok","httpCode","message","dates"],"properties":{"unitId":{"type":"string"},"ratePlanId":{"type":"string","nullable":true},"ok":{"description":"The portal accepted the change and the local mirror was updated.","type":"boolean"},"httpCode":{"description":"HTTP status returned by the portal.","type":"integer"},"message":{"description":"Portal message (error details when `ok` is false).","type":"string"},"dates":{"description":"Number of dates affected.","type":"integer"}},"type":"object"}}},"paths":{"/rooms/{roomId}/photos":{"post":{"tags":["rooms"],"summary":"Upload room photos (base64)","description":"Appends 1–3 photos to the room's gallery (see RoomPhotoUploadRequest: base64, max 7 MB each, JPEG/PNG/GIF/WebP, gallery max 8). Returns the created photos with their URLs. For a room that belongs to a room type this returns 422 — upload to /room-types/{roomTypeId}/photos instead (rooms of a type share the type's gallery). Required scope: `roomPhotos.create`.","operationId":"addRoomPhotos","parameters":[{"name":"roomId","in":"path","description":"Room ID (GET /rooms)","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomPhotoUploadRequest"}}}},"responses":{"201":{"description":"Uploaded photos","headers":{"Location":{"description":"URL of the gallery","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomPhotosResponse"}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem400"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem401"}}}},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem403"}}}},"404":{"description":"Room not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem404"}}}},"422":{"description":"Validation error — not an image, unsupported format, too large, gallery full, room belongs to a room type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem422"}}}},"502":{"description":"Photo storage (S3) rejected the upload — photos listed in `created` WERE saved, the failed file was not","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem502"}}}}}}}}}
```

## Delete room photo

> Deletes the photo from storage and the gallery; remaining photos keep a contiguous order. 422 for a room that belongs to a room type. Required scope: \`roomPhotos.delete\`.

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"rooms","description":"Create, read, update, and delete operations for rooms."}],"servers":[{"url":"https://api.mobile-calendar.com/v1/public","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"Pass the token in header: Authorization: Bearer <JWT>","bearerFormat":"JWT","scheme":"bearer"}},"schemas":{"ApiProblem401":{"description":"Missing authentication or invalid credentials.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"401 Unauthorized","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblemBase":{"description":"RFC 7807 Problem Details (base schema).","required":["type","title","status","detail","instance"],"properties":{"type":{"description":"RFC 7807 base — DO NOT use directly in endpoints.\nIn endpoints reference ApiProblem400/401/403/409/422/429/500.","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"},"ApiProblem403":{"description":"Authenticated user but no access.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"403 Forbidden","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem404":{"description":"Resource specified in the request was not found.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"404 Not Found","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem422":{"description":"Input data validation error.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"422 Unprocessable Entity (validation)","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"},"errors":{"description":"Map: field name → error message(s)","type":"object","additionalProperties":{"$ref":"#/components/schemas/ErrorValue"}}},"type":"object"}]},"ErrorValue":{"description":"Single error message or list of messages.","oneOf":[{"description":"Error value in error map can be:\n- single string, or\n- array of strings (multiple messages for one field).","type":"string"},{"type":"array","items":{"type":"string"}}]}}},"paths":{"/rooms/{roomId}/photos/{photoId}":{"delete":{"tags":["rooms"],"summary":"Delete room photo","description":"Deletes the photo from storage and the gallery; remaining photos keep a contiguous order. 422 for a room that belongs to a room type. Required scope: `roomPhotos.delete`.","operationId":"deleteRoomPhoto","parameters":[{"name":"roomId","in":"path","description":"Room ID (GET /rooms)","required":true,"schema":{"type":"integer","minimum":1}},{"name":"photoId","in":"path","description":"Photo ID (from the gallery listing)","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"204":{"description":"Deleted","headers":{"RUID":{"description":"Request unique identifier","schema":{"type":"string"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem401"}}}},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem403"}}}},"404":{"description":"Room or photo not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem404"}}}},"422":{"description":"Room belongs to a room type — delete via /room-types/{roomTypeId}/photos","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem422"}}}}}}}}}
```

## Change room photo order

> Moves the photo to the given position (1 = cover photo shown first in the panel and the booking engine); other photos shift and the order stays contiguous. Returns the full reordered gallery. 422 for a room that belongs to a room type (manage the type's gallery). Required scope: \`roomPhotos.update\`.

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"rooms","description":"Create, read, update, and delete operations for rooms."}],"servers":[{"url":"https://api.mobile-calendar.com/v1/public","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"Pass the token in header: Authorization: Bearer <JWT>","bearerFormat":"JWT","scheme":"bearer"}},"schemas":{"RoomPhotoUpdateRequest":{"description":"Moves the photo to the given gallery position; other photos shift accordingly. A position beyond the gallery size is clamped to the last position.","required":["order"],"properties":{"order":{"description":"Target position (1 = first/cover photo).","type":"integer","maximum":100,"minimum":1}},"type":"object"},"RoomPhotosResponse":{"required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RoomPhotoEntry"}},"meta":{"type":"object","allOf":[{"$ref":"#/components/schemas/MetaWithRuid"},{"properties":{"roomId":{"description":"Present for room endpoints.","type":"integer"},"roomTypeId":{"description":"Present for room-type endpoints.","type":"integer"},"inheritedFromRoomTypeId":{"description":"Room endpoints only: set when the room belongs to a room type — the listed photos are the TYPE's gallery (manage them via /room-types/{id}/photos).","type":"integer","nullable":true},"totalPhotos":{"type":"integer"},"maxPhotos":{"type":"integer"}},"type":"object"}]}},"type":"object"},"RoomPhotoEntry":{"description":"One photo of a room / room type gallery (shown in the panel, the booking engine and used when exporting listings to OTAs). Photos are stored resized to max 1200 px.","required":["photoId","order","url","fileName","addDate"],"properties":{"photoId":{"type":"integer"},"order":{"description":"Position in the gallery (1 = first/cover photo); always contiguous 1..n.","type":"integer","minimum":1},"url":{"description":"Public URL of the image.","type":"string"},"fileName":{"type":"string"},"addDate":{"type":"string","format":"date-time","nullable":true}},"type":"object"},"MetaWithRuid":{"description":"Metadata with request tracking.","required":["ruid"],"properties":{"ruid":{"description":"Request Unique ID","type":"string","format":"uuid"}},"type":"object"},"ApiProblem400":{"description":"Request syntax/format error.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"400 Bad Request","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblemBase":{"description":"RFC 7807 Problem Details (base schema).","required":["type","title","status","detail","instance"],"properties":{"type":{"description":"RFC 7807 base — DO NOT use directly in endpoints.\nIn endpoints reference ApiProblem400/401/403/409/422/429/500.","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"},"ApiProblem401":{"description":"Missing authentication or invalid credentials.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"401 Unauthorized","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem403":{"description":"Authenticated user but no access.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"403 Forbidden","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem404":{"description":"Resource specified in the request was not found.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"404 Not Found","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem422":{"description":"Input data validation error.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"422 Unprocessable Entity (validation)","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"},"errors":{"description":"Map: field name → error message(s)","type":"object","additionalProperties":{"$ref":"#/components/schemas/ErrorValue"}}},"type":"object"}]},"ErrorValue":{"description":"Single error message or list of messages.","oneOf":[{"description":"Error value in error map can be:\n- single string, or\n- array of strings (multiple messages for one field).","type":"string"},{"type":"array","items":{"type":"string"}}]}}},"paths":{"/rooms/{roomId}/photos/{photoId}":{"patch":{"tags":["rooms"],"summary":"Change room photo order","description":"Moves the photo to the given position (1 = cover photo shown first in the panel and the booking engine); other photos shift and the order stays contiguous. Returns the full reordered gallery. 422 for a room that belongs to a room type (manage the type's gallery). Required scope: `roomPhotos.update`.","operationId":"updateRoomPhoto","parameters":[{"name":"roomId","in":"path","description":"Room ID (GET /rooms)","required":true,"schema":{"type":"integer","minimum":1}},{"name":"photoId","in":"path","description":"Photo ID (from the gallery listing)","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomPhotoUpdateRequest"}}}},"responses":{"200":{"description":"Reordered gallery","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomPhotosResponse"}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem400"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem401"}}}},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem403"}}}},"404":{"description":"Room or photo not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem404"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem422"}}}}}}}}}
```

## Get room type photos

> Returns the room type's photo gallery (shared by every room of the type) ordered by \`order\`. Required scope: \`roomPhotos.read\`.

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"rooms","description":"Create, read, update, and delete operations for rooms."}],"servers":[{"url":"https://api.mobile-calendar.com/v1/public","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"Pass the token in header: Authorization: Bearer <JWT>","bearerFormat":"JWT","scheme":"bearer"}},"schemas":{"RoomPhotosResponse":{"required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RoomPhotoEntry"}},"meta":{"type":"object","allOf":[{"$ref":"#/components/schemas/MetaWithRuid"},{"properties":{"roomId":{"description":"Present for room endpoints.","type":"integer"},"roomTypeId":{"description":"Present for room-type endpoints.","type":"integer"},"inheritedFromRoomTypeId":{"description":"Room endpoints only: set when the room belongs to a room type — the listed photos are the TYPE's gallery (manage them via /room-types/{id}/photos).","type":"integer","nullable":true},"totalPhotos":{"type":"integer"},"maxPhotos":{"type":"integer"}},"type":"object"}]}},"type":"object"},"RoomPhotoEntry":{"description":"One photo of a room / room type gallery (shown in the panel, the booking engine and used when exporting listings to OTAs). Photos are stored resized to max 1200 px.","required":["photoId","order","url","fileName","addDate"],"properties":{"photoId":{"type":"integer"},"order":{"description":"Position in the gallery (1 = first/cover photo); always contiguous 1..n.","type":"integer","minimum":1},"url":{"description":"Public URL of the image.","type":"string"},"fileName":{"type":"string"},"addDate":{"type":"string","format":"date-time","nullable":true}},"type":"object"},"MetaWithRuid":{"description":"Metadata with request tracking.","required":["ruid"],"properties":{"ruid":{"description":"Request Unique ID","type":"string","format":"uuid"}},"type":"object"},"ApiProblem401":{"description":"Missing authentication or invalid credentials.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"401 Unauthorized","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblemBase":{"description":"RFC 7807 Problem Details (base schema).","required":["type","title","status","detail","instance"],"properties":{"type":{"description":"RFC 7807 base — DO NOT use directly in endpoints.\nIn endpoints reference ApiProblem400/401/403/409/422/429/500.","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"},"ApiProblem403":{"description":"Authenticated user but no access.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"403 Forbidden","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem404":{"description":"Resource specified in the request was not found.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"404 Not Found","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]}}},"paths":{"/room-types/{roomTypeId}/photos":{"get":{"tags":["rooms"],"summary":"Get room type photos","description":"Returns the room type's photo gallery (shared by every room of the type) ordered by `order`. Required scope: `roomPhotos.read`.","operationId":"getRoomTypePhotos","parameters":[{"name":"roomTypeId","in":"path","description":"Room type ID (GET /room-types)","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Photos of the room type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomPhotosResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem401"}}}},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem403"}}}},"404":{"description":"Room type not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem404"}}}}}}}}}
```

## Upload room type photos (base64)

> Appends 1–3 photos to the room type's gallery — shared by every room of the type (see RoomPhotoUploadRequest; gallery max 8). Required scope: \`roomPhotos.create\`.

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"rooms","description":"Create, read, update, and delete operations for rooms."}],"servers":[{"url":"https://api.mobile-calendar.com/v1/public","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"Pass the token in header: Authorization: Bearer <JWT>","bearerFormat":"JWT","scheme":"bearer"}},"schemas":{"RoomPhotoUploadRequest":{"description":"Upload of 1–3 images per request as base64 (plain base64 or a data URI like `data:image/jpeg;base64,…`). Each image: max 7 MB decoded, format JPEG/PNG/GIF/WebP, verified to be a real image and re-encoded server-side (resized to max 1200 px). The gallery holds at most 8 photos — the upload is rejected (422) when it would exceed that. New photos are appended at the end of the gallery.","required":["photos"],"properties":{"photos":{"type":"array","items":{"required":["data"],"properties":{"data":{"description":"Base64-encoded image (data URI accepted).","type":"string","format":"byte"}},"type":"object"},"maxItems":3,"minItems":1}},"type":"object"},"RoomPhotosResponse":{"required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RoomPhotoEntry"}},"meta":{"type":"object","allOf":[{"$ref":"#/components/schemas/MetaWithRuid"},{"properties":{"roomId":{"description":"Present for room endpoints.","type":"integer"},"roomTypeId":{"description":"Present for room-type endpoints.","type":"integer"},"inheritedFromRoomTypeId":{"description":"Room endpoints only: set when the room belongs to a room type — the listed photos are the TYPE's gallery (manage them via /room-types/{id}/photos).","type":"integer","nullable":true},"totalPhotos":{"type":"integer"},"maxPhotos":{"type":"integer"}},"type":"object"}]}},"type":"object"},"RoomPhotoEntry":{"description":"One photo of a room / room type gallery (shown in the panel, the booking engine and used when exporting listings to OTAs). Photos are stored resized to max 1200 px.","required":["photoId","order","url","fileName","addDate"],"properties":{"photoId":{"type":"integer"},"order":{"description":"Position in the gallery (1 = first/cover photo); always contiguous 1..n.","type":"integer","minimum":1},"url":{"description":"Public URL of the image.","type":"string"},"fileName":{"type":"string"},"addDate":{"type":"string","format":"date-time","nullable":true}},"type":"object"},"MetaWithRuid":{"description":"Metadata with request tracking.","required":["ruid"],"properties":{"ruid":{"description":"Request Unique ID","type":"string","format":"uuid"}},"type":"object"},"ApiProblem400":{"description":"Request syntax/format error.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"400 Bad Request","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblemBase":{"description":"RFC 7807 Problem Details (base schema).","required":["type","title","status","detail","instance"],"properties":{"type":{"description":"RFC 7807 base — DO NOT use directly in endpoints.\nIn endpoints reference ApiProblem400/401/403/409/422/429/500.","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"},"ApiProblem401":{"description":"Missing authentication or invalid credentials.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"401 Unauthorized","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem403":{"description":"Authenticated user but no access.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"403 Forbidden","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem404":{"description":"Resource specified in the request was not found.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"404 Not Found","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem422":{"description":"Input data validation error.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"422 Unprocessable Entity (validation)","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"},"errors":{"description":"Map: field name → error message(s)","type":"object","additionalProperties":{"$ref":"#/components/schemas/ErrorValue"}}},"type":"object"}]},"ErrorValue":{"description":"Single error message or list of messages.","oneOf":[{"description":"Error value in error map can be:\n- single string, or\n- array of strings (multiple messages for one field).","type":"string"},{"type":"array","items":{"type":"string"}}]},"ApiProblem502":{"description":"The external portal (OTA) did not accept the change; nothing was saved locally for the failed units.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"title":{"description":"502 Bad Gateway (OTA portal rejected the request)","type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"channel":{"type":"string"},"results":{"description":"Outcome per unit — `ok: true` entries WERE applied (partial success is possible when several units are sent).","type":"array","items":{"$ref":"#/components/schemas/ChannelPricingResult"}}},"type":"object"}]},"ChannelPricingResult":{"required":["unitId","ratePlanId","ok","httpCode","message","dates"],"properties":{"unitId":{"type":"string"},"ratePlanId":{"type":"string","nullable":true},"ok":{"description":"The portal accepted the change and the local mirror was updated.","type":"boolean"},"httpCode":{"description":"HTTP status returned by the portal.","type":"integer"},"message":{"description":"Portal message (error details when `ok` is false).","type":"string"},"dates":{"description":"Number of dates affected.","type":"integer"}},"type":"object"}}},"paths":{"/room-types/{roomTypeId}/photos":{"post":{"tags":["rooms"],"summary":"Upload room type photos (base64)","description":"Appends 1–3 photos to the room type's gallery — shared by every room of the type (see RoomPhotoUploadRequest; gallery max 8). Required scope: `roomPhotos.create`.","operationId":"addRoomTypePhotos","parameters":[{"name":"roomTypeId","in":"path","description":"Room type ID (GET /room-types)","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomPhotoUploadRequest"}}}},"responses":{"201":{"description":"Uploaded photos","headers":{"Location":{"description":"URL of the gallery","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomPhotosResponse"}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem400"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem401"}}}},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem403"}}}},"404":{"description":"Room type not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem404"}}}},"422":{"description":"Validation error — not an image, unsupported format, too large, gallery full","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem422"}}}},"502":{"description":"Photo storage (S3) rejected the upload — photos listed in `created` WERE saved, the failed file was not","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem502"}}}}}}}}}
```

## Delete room type photo

> Deletes the photo from storage and the type's gallery (affects every room of the type). Required scope: \`roomPhotos.delete\`.

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"rooms","description":"Create, read, update, and delete operations for rooms."}],"servers":[{"url":"https://api.mobile-calendar.com/v1/public","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"Pass the token in header: Authorization: Bearer <JWT>","bearerFormat":"JWT","scheme":"bearer"}},"schemas":{"ApiProblem401":{"description":"Missing authentication or invalid credentials.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"401 Unauthorized","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblemBase":{"description":"RFC 7807 Problem Details (base schema).","required":["type","title","status","detail","instance"],"properties":{"type":{"description":"RFC 7807 base — DO NOT use directly in endpoints.\nIn endpoints reference ApiProblem400/401/403/409/422/429/500.","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"},"ApiProblem403":{"description":"Authenticated user but no access.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"403 Forbidden","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem404":{"description":"Resource specified in the request was not found.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"404 Not Found","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]}}},"paths":{"/room-types/{roomTypeId}/photos/{photoId}":{"delete":{"tags":["rooms"],"summary":"Delete room type photo","description":"Deletes the photo from storage and the type's gallery (affects every room of the type). Required scope: `roomPhotos.delete`.","operationId":"deleteRoomTypePhoto","parameters":[{"name":"roomTypeId","in":"path","description":"Room type ID (GET /room-types)","required":true,"schema":{"type":"integer","minimum":1}},{"name":"photoId","in":"path","description":"Photo ID (from the gallery listing)","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"204":{"description":"Deleted","headers":{"RUID":{"description":"Request unique identifier","schema":{"type":"string"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem401"}}}},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem403"}}}},"404":{"description":"Room type or photo not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem404"}}}}}}}}}
```

## Change room type photo order

> Moves the photo to the given position within the type's gallery (1 = cover). Returns the full reordered gallery. Required scope: \`roomPhotos.update\`.

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"rooms","description":"Create, read, update, and delete operations for rooms."}],"servers":[{"url":"https://api.mobile-calendar.com/v1/public","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"Pass the token in header: Authorization: Bearer <JWT>","bearerFormat":"JWT","scheme":"bearer"}},"schemas":{"RoomPhotoUpdateRequest":{"description":"Moves the photo to the given gallery position; other photos shift accordingly. A position beyond the gallery size is clamped to the last position.","required":["order"],"properties":{"order":{"description":"Target position (1 = first/cover photo).","type":"integer","maximum":100,"minimum":1}},"type":"object"},"RoomPhotosResponse":{"required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RoomPhotoEntry"}},"meta":{"type":"object","allOf":[{"$ref":"#/components/schemas/MetaWithRuid"},{"properties":{"roomId":{"description":"Present for room endpoints.","type":"integer"},"roomTypeId":{"description":"Present for room-type endpoints.","type":"integer"},"inheritedFromRoomTypeId":{"description":"Room endpoints only: set when the room belongs to a room type — the listed photos are the TYPE's gallery (manage them via /room-types/{id}/photos).","type":"integer","nullable":true},"totalPhotos":{"type":"integer"},"maxPhotos":{"type":"integer"}},"type":"object"}]}},"type":"object"},"RoomPhotoEntry":{"description":"One photo of a room / room type gallery (shown in the panel, the booking engine and used when exporting listings to OTAs). Photos are stored resized to max 1200 px.","required":["photoId","order","url","fileName","addDate"],"properties":{"photoId":{"type":"integer"},"order":{"description":"Position in the gallery (1 = first/cover photo); always contiguous 1..n.","type":"integer","minimum":1},"url":{"description":"Public URL of the image.","type":"string"},"fileName":{"type":"string"},"addDate":{"type":"string","format":"date-time","nullable":true}},"type":"object"},"MetaWithRuid":{"description":"Metadata with request tracking.","required":["ruid"],"properties":{"ruid":{"description":"Request Unique ID","type":"string","format":"uuid"}},"type":"object"},"ApiProblem400":{"description":"Request syntax/format error.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"400 Bad Request","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblemBase":{"description":"RFC 7807 Problem Details (base schema).","required":["type","title","status","detail","instance"],"properties":{"type":{"description":"RFC 7807 base — DO NOT use directly in endpoints.\nIn endpoints reference ApiProblem400/401/403/409/422/429/500.","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"},"ApiProblem401":{"description":"Missing authentication or invalid credentials.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"401 Unauthorized","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem403":{"description":"Authenticated user but no access.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"403 Forbidden","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem404":{"description":"Resource specified in the request was not found.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"404 Not Found","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem422":{"description":"Input data validation error.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"422 Unprocessable Entity (validation)","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"},"errors":{"description":"Map: field name → error message(s)","type":"object","additionalProperties":{"$ref":"#/components/schemas/ErrorValue"}}},"type":"object"}]},"ErrorValue":{"description":"Single error message or list of messages.","oneOf":[{"description":"Error value in error map can be:\n- single string, or\n- array of strings (multiple messages for one field).","type":"string"},{"type":"array","items":{"type":"string"}}]}}},"paths":{"/room-types/{roomTypeId}/photos/{photoId}":{"patch":{"tags":["rooms"],"summary":"Change room type photo order","description":"Moves the photo to the given position within the type's gallery (1 = cover). Returns the full reordered gallery. Required scope: `roomPhotos.update`.","operationId":"updateRoomTypePhoto","parameters":[{"name":"roomTypeId","in":"path","description":"Room type ID (GET /room-types)","required":true,"schema":{"type":"integer","minimum":1}},{"name":"photoId","in":"path","description":"Photo ID (from the gallery listing)","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomPhotoUpdateRequest"}}}},"responses":{"200":{"description":"Reordered gallery","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomPhotosResponse"}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem400"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem401"}}}},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem403"}}}},"404":{"description":"Room type or photo not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem404"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem422"}}}}}}}}}
```

## GET /rooms

> List rooms (paginated)

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"rooms","description":"Create, read, update, and delete operations for rooms."}],"servers":[{"url":"https://api.mobile-calendar.com/v1/public","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"Pass the token in header: Authorization: Bearer <JWT>","bearerFormat":"JWT","scheme":"bearer"}},"parameters":{"PageParam":{"name":"page","in":"query","description":"Page number (>=1)","required":false,"schema":{"type":"integer","default":1,"minimum":1}},"LimitParam":{"name":"limit","in":"query","description":"Page size (1–200)","required":false,"schema":{"type":"integer","default":25,"maximum":200,"minimum":1}}},"schemas":{"RoomListResponse":{"description":"Paginated list of rooms","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Room"}},"meta":{"$ref":"#/components/schemas/MetaWithPagination"}},"type":"object"},"Room":{"description":"Public room representation","required":["roomId","name","persons","maxAdults","maxChildren","roomType","roomTypeId","singleBed","doubleBed","extraBed","service","shareInOffer"],"properties":{"roomId":{"type":"integer"},"name":{"type":"string"},"persons":{"description":"Base number of persons (room capacity); must be >= maxAdults.","type":"integer"},"maxAdults":{"description":"Max adults the room hosts. Capacity rule: persons >= maxAdults > maxChildren (the room must fit at least one adult more than children — a child never stays without an accompanying adult).","type":"integer"},"maxChildren":{"description":"Max children. Must be STRICTLY LOWER than maxAdults (maxChildren < maxAdults): with maxAdults=4 the allowed range is 0-3. Use 0 for an adults-only room; if the room really takes more children, raise maxAdults and persons.","type":"integer"},"roomType":{"description":"Enum (internal)","type":"string"},"roomTypeId":{"type":"integer","nullable":true},"singleBed":{"type":"integer"},"doubleBed":{"type":"integer"},"extraBed":{"type":"integer"},"service":{"description":"Service status enum","type":"string","enum":["CLEAN","DIRTY","WHILE_CLEANING"]},"serviceInfo":{"type":"string","nullable":true},"info":{"type":"string","nullable":true},"color":{"type":"string","nullable":true},"square":{"type":"string"},"squareType":{"type":"string"},"description":{"description":"Localized description map. Key 'default' is required. Other keys = ISO language codes.","type":"object","additionalProperties":{"type":"string"}},"equipment":{"type":"array","items":{"type":"string","enum":["AIR_CONDITIONING","KITCHENETTE","TELEVISION","BATHROOM","POOL","BALCONY","WASHING_MACHINE","KITCHEN_UTENSILS","BABY_CRIB","BLANKET","SCREEN","UMBRELLA","LOUNGER","CLOTHES_HANGER","DRYER","TOWELS","FLOOR_COVERING","FIREPLACE","HEATING","IRON","SAFE","DESK","BIDET","TOOTHBRUSH","SHAMPOO","HAIR_CONDITIONER","SOAP","RADIO","TABLE","WINE_GLASSES","CORKSCREW","WATER","GRILL","DISHWASHER","MINIBAR","REFRIGERATOR","COFFEE_MACHINE"]}},"shareInOffer":{"type":"integer","enum":[0,1]},"locationId":{"type":"integer","nullable":true}},"type":"object"},"MetaWithPagination":{"description":"Pagination parameters with request tracking in list responses.","required":["totalItems","currentPage","totalPages","itemsPerPage","ruid"],"properties":{"totalItems":{"type":"integer","minimum":0},"currentPage":{"type":"integer","minimum":1},"totalPages":{"type":"integer","minimum":0},"itemsPerPage":{"type":"integer","minimum":1},"ruid":{"description":"Request Unique ID","type":"string","format":"uuid"}},"type":"object"},"ApiProblem401":{"description":"Missing authentication or invalid credentials.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"401 Unauthorized","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblemBase":{"description":"RFC 7807 Problem Details (base schema).","required":["type","title","status","detail","instance"],"properties":{"type":{"description":"RFC 7807 base — DO NOT use directly in endpoints.\nIn endpoints reference ApiProblem400/401/403/409/422/429/500.","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"},"ApiProblem422":{"description":"Input data validation error.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"422 Unprocessable Entity (validation)","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"},"errors":{"description":"Map: field name → error message(s)","type":"object","additionalProperties":{"$ref":"#/components/schemas/ErrorValue"}}},"type":"object"}]},"ErrorValue":{"description":"Single error message or list of messages.","oneOf":[{"description":"Error value in error map can be:\n- single string, or\n- array of strings (multiple messages for one field).","type":"string"},{"type":"array","items":{"type":"string"}}]}}},"paths":{"/rooms":{"get":{"tags":["rooms"],"summary":"List rooms (paginated)","operationId":"listRooms","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"},{"name":"name","in":"query","description":"Filter by a fragment of the room name. Case-, whitespace- and diacritics-insensitive (\"pokoj gorski\" finds \"Pokój Górski 1\"); pass the name exactly as the user wrote it.","required":false,"schema":{"type":"string"}},{"name":"description","in":"query","description":"Filter by partial description (default / any language key)","required":false,"schema":{"type":"string"}},{"name":"equipment","in":"query","description":"Rooms having ANY of the listed equipment (OR). A room assigned to a room type has the type's equipment. Send as `?equipment[]=BALCONY&equipment[]=TELEVISION` or `?equipment=BALCONY,TELEVISION`; an unknown name → 422. To find a FREE room with some equipment, filter here and check the returned rooms with GET /availability.","required":false,"schema":{"type":"array","items":{"type":"string","enum":["AIR_CONDITIONING","KITCHENETTE","TELEVISION","BATHROOM","POOL","BALCONY","WASHING_MACHINE","KITCHEN_UTENSILS","BABY_CRIB","BLANKET","SCREEN","UMBRELLA","LOUNGER","CLOTHES_HANGER","DRYER","TOWELS","FLOOR_COVERING","FIREPLACE","HEATING","IRON","SAFE","DESK","BIDET","TOOTHBRUSH","SHAMPOO","HAIR_CONDITIONER","SOAP","RADIO","TABLE","WINE_GLASSES","CORKSCREW","WATER","GRILL","DISHWASHER","MINIBAR","REFRIGERATOR","COFFEE_MACHINE"]}}},{"name":"roomId","in":"query","description":"Return only these rooms. One id, a comma-separated list (`?roomId=12,15`) or a repeated parameter (`?roomId[]=12&roomId[]=15`). Use it to fetch a known set of rooms in one call instead of paging the whole property.","required":false,"schema":{"oneOf":[{"type":"integer"},{"type":"array","items":{"type":"integer"}}]}},{"name":"roomTypeId","in":"query","description":"Filter by room type ID","required":false,"schema":{"type":"integer"}},{"name":"service","in":"query","description":"Housekeeping status of the room.","required":false,"schema":{"type":"string","enum":["CLEAN","DIRTY","WHILE_CLEANING"]}},{"name":"persons","in":"query","description":"Filter by base number of persons","required":false,"schema":{"type":"integer","minimum":0}},{"name":"maxAdults","in":"query","description":"Filter by max adults","required":false,"schema":{"type":"integer","minimum":0}},{"name":"maxChildren","in":"query","description":"Filter by max children","required":false,"schema":{"type":"integer","minimum":0}},{"name":"singleBed","in":"query","description":"Filter by number of single beds","required":false,"schema":{"type":"integer","minimum":0}},{"name":"doubleBed","in":"query","description":"Filter by number of double beds","required":false,"schema":{"type":"integer","minimum":0}},{"name":"extraBed","in":"query","description":"Filter by number of extra beds","required":false,"schema":{"type":"integer","minimum":0}},{"name":"isLocked","in":"query","description":"Whether room is locked (0/1)","required":false,"schema":{"type":"boolean"}},{"name":"shareInOffer","in":"query","description":"Whether room is shared in offer (0/1)","required":false,"schema":{"type":"boolean"}},{"name":"addDate","in":"query","description":"Filter by creation date (YYYY-MM-DD)","required":false,"schema":{"type":"string","format":"date"}},{"name":"editDate","in":"query","description":"Filter by last edit date (YYYY-MM-DD)","required":false,"schema":{"type":"string","format":"date"}},{"name":"lastEdit","in":"query","description":"Filter: edited since Unix timestamp","required":false,"schema":{"type":"integer"}},{"name":"orderBy","in":"query","description":"Sort field","required":false,"schema":{"type":"string","default":"roomId","enum":["roomId","name","persons","addDate","editDate"]}},{"name":"orderType","in":"query","description":"Sort direction","required":false,"schema":{"type":"string","default":"ASC","enum":["ASC","DESC"]}}],"responses":{"200":{"description":"Room list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomListResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem401"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem422"}}}}}}}}}
```

## POST /rooms

> Create room

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"rooms","description":"Create, read, update, and delete operations for rooms."}],"servers":[{"url":"https://api.mobile-calendar.com/v1/public","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"Pass the token in header: Authorization: Bearer <JWT>","bearerFormat":"JWT","scheme":"bearer"}},"schemas":{"RoomCreateRequest":{"description":"Create room payload","required":["name","persons","maxAdults","maxChildren","singleBed","doubleBed"],"properties":{"name":{"type":"string"},"persons":{"description":"Base number of persons (room capacity); must be >= maxAdults.","type":"integer"},"maxAdults":{"description":"Max adults the room hosts. Capacity rule: persons >= maxAdults > maxChildren (the room must fit at least one adult more than children — a child never stays without an accompanying adult).","type":"integer"},"maxChildren":{"description":"Max children. Must be STRICTLY LOWER than maxAdults (maxChildren < maxAdults): with maxAdults=4 the allowed range is 0-3. Use 0 for an adults-only room; if the room really takes more children, raise maxAdults and persons.","type":"integer"},"roomTypeId":{"type":"integer","nullable":true},"roomType":{"type":"string","nullable":true},"singleBed":{"type":"integer"},"doubleBed":{"type":"integer"},"extraBed":{"type":"integer"},"service":{"type":"string"},"serviceInfo":{"type":"string","nullable":true},"info":{"type":"string","nullable":true},"color":{"type":"string","nullable":true},"square":{"type":"string"},"squareType":{"type":"string"},"description":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":{"type":"string"}}]},"equipment":{"type":"array","items":{"type":"string"}},"shareInOffer":{"type":"integer","enum":[0,1]},"locationId":{"type":"integer","nullable":true}},"type":"object"},"RoomCreateResponse":{"description":"Created room response","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Room"},"meta":{"$ref":"#/components/schemas/MetaWithRuid"}},"type":"object"},"Room":{"description":"Public room representation","required":["roomId","name","persons","maxAdults","maxChildren","roomType","roomTypeId","singleBed","doubleBed","extraBed","service","shareInOffer"],"properties":{"roomId":{"type":"integer"},"name":{"type":"string"},"persons":{"description":"Base number of persons (room capacity); must be >= maxAdults.","type":"integer"},"maxAdults":{"description":"Max adults the room hosts. Capacity rule: persons >= maxAdults > maxChildren (the room must fit at least one adult more than children — a child never stays without an accompanying adult).","type":"integer"},"maxChildren":{"description":"Max children. Must be STRICTLY LOWER than maxAdults (maxChildren < maxAdults): with maxAdults=4 the allowed range is 0-3. Use 0 for an adults-only room; if the room really takes more children, raise maxAdults and persons.","type":"integer"},"roomType":{"description":"Enum (internal)","type":"string"},"roomTypeId":{"type":"integer","nullable":true},"singleBed":{"type":"integer"},"doubleBed":{"type":"integer"},"extraBed":{"type":"integer"},"service":{"description":"Service status enum","type":"string","enum":["CLEAN","DIRTY","WHILE_CLEANING"]},"serviceInfo":{"type":"string","nullable":true},"info":{"type":"string","nullable":true},"color":{"type":"string","nullable":true},"square":{"type":"string"},"squareType":{"type":"string"},"description":{"description":"Localized description map. Key 'default' is required. Other keys = ISO language codes.","type":"object","additionalProperties":{"type":"string"}},"equipment":{"type":"array","items":{"type":"string","enum":["AIR_CONDITIONING","KITCHENETTE","TELEVISION","BATHROOM","POOL","BALCONY","WASHING_MACHINE","KITCHEN_UTENSILS","BABY_CRIB","BLANKET","SCREEN","UMBRELLA","LOUNGER","CLOTHES_HANGER","DRYER","TOWELS","FLOOR_COVERING","FIREPLACE","HEATING","IRON","SAFE","DESK","BIDET","TOOTHBRUSH","SHAMPOO","HAIR_CONDITIONER","SOAP","RADIO","TABLE","WINE_GLASSES","CORKSCREW","WATER","GRILL","DISHWASHER","MINIBAR","REFRIGERATOR","COFFEE_MACHINE"]}},"shareInOffer":{"type":"integer","enum":[0,1]},"locationId":{"type":"integer","nullable":true}},"type":"object"},"MetaWithRuid":{"description":"Metadata with request tracking.","required":["ruid"],"properties":{"ruid":{"description":"Request Unique ID","type":"string","format":"uuid"}},"type":"object"},"ApiProblem400":{"description":"Request syntax/format error.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"400 Bad Request","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblemBase":{"description":"RFC 7807 Problem Details (base schema).","required":["type","title","status","detail","instance"],"properties":{"type":{"description":"RFC 7807 base — DO NOT use directly in endpoints.\nIn endpoints reference ApiProblem400/401/403/409/422/429/500.","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"},"ApiProblem401":{"description":"Missing authentication or invalid credentials.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"401 Unauthorized","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem422":{"description":"Input data validation error.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"422 Unprocessable Entity (validation)","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"},"errors":{"description":"Map: field name → error message(s)","type":"object","additionalProperties":{"$ref":"#/components/schemas/ErrorValue"}}},"type":"object"}]},"ErrorValue":{"description":"Single error message or list of messages.","oneOf":[{"description":"Error value in error map can be:\n- single string, or\n- array of strings (multiple messages for one field).","type":"string"},{"type":"array","items":{"type":"string"}}]}}},"paths":{"/rooms":{"post":{"tags":["rooms"],"summary":"Create room","operationId":"createRoom","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomCreateRequest"}}}},"responses":{"201":{"description":"Created","headers":{"Location":{"description":"URL of the new resource","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomCreateResponse"}}}},"400":{"description":"Invalid data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem400"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem401"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem422"}}}}}}}}}
```

## GET /rooms/{id}

> Get room by ID

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"rooms","description":"Create, read, update, and delete operations for rooms."}],"servers":[{"url":"https://api.mobile-calendar.com/v1/public","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"Pass the token in header: Authorization: Bearer <JWT>","bearerFormat":"JWT","scheme":"bearer"}},"schemas":{"RoomResponse":{"description":"Single room response","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Room"},"meta":{"$ref":"#/components/schemas/MetaWithRuid"}},"type":"object"},"Room":{"description":"Public room representation","required":["roomId","name","persons","maxAdults","maxChildren","roomType","roomTypeId","singleBed","doubleBed","extraBed","service","shareInOffer"],"properties":{"roomId":{"type":"integer"},"name":{"type":"string"},"persons":{"description":"Base number of persons (room capacity); must be >= maxAdults.","type":"integer"},"maxAdults":{"description":"Max adults the room hosts. Capacity rule: persons >= maxAdults > maxChildren (the room must fit at least one adult more than children — a child never stays without an accompanying adult).","type":"integer"},"maxChildren":{"description":"Max children. Must be STRICTLY LOWER than maxAdults (maxChildren < maxAdults): with maxAdults=4 the allowed range is 0-3. Use 0 for an adults-only room; if the room really takes more children, raise maxAdults and persons.","type":"integer"},"roomType":{"description":"Enum (internal)","type":"string"},"roomTypeId":{"type":"integer","nullable":true},"singleBed":{"type":"integer"},"doubleBed":{"type":"integer"},"extraBed":{"type":"integer"},"service":{"description":"Service status enum","type":"string","enum":["CLEAN","DIRTY","WHILE_CLEANING"]},"serviceInfo":{"type":"string","nullable":true},"info":{"type":"string","nullable":true},"color":{"type":"string","nullable":true},"square":{"type":"string"},"squareType":{"type":"string"},"description":{"description":"Localized description map. Key 'default' is required. Other keys = ISO language codes.","type":"object","additionalProperties":{"type":"string"}},"equipment":{"type":"array","items":{"type":"string","enum":["AIR_CONDITIONING","KITCHENETTE","TELEVISION","BATHROOM","POOL","BALCONY","WASHING_MACHINE","KITCHEN_UTENSILS","BABY_CRIB","BLANKET","SCREEN","UMBRELLA","LOUNGER","CLOTHES_HANGER","DRYER","TOWELS","FLOOR_COVERING","FIREPLACE","HEATING","IRON","SAFE","DESK","BIDET","TOOTHBRUSH","SHAMPOO","HAIR_CONDITIONER","SOAP","RADIO","TABLE","WINE_GLASSES","CORKSCREW","WATER","GRILL","DISHWASHER","MINIBAR","REFRIGERATOR","COFFEE_MACHINE"]}},"shareInOffer":{"type":"integer","enum":[0,1]},"locationId":{"type":"integer","nullable":true}},"type":"object"},"MetaWithRuid":{"description":"Metadata with request tracking.","required":["ruid"],"properties":{"ruid":{"description":"Request Unique ID","type":"string","format":"uuid"}},"type":"object"},"ApiProblem401":{"description":"Missing authentication or invalid credentials.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"401 Unauthorized","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblemBase":{"description":"RFC 7807 Problem Details (base schema).","required":["type","title","status","detail","instance"],"properties":{"type":{"description":"RFC 7807 base — DO NOT use directly in endpoints.\nIn endpoints reference ApiProblem400/401/403/409/422/429/500.","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"},"ApiProblem404":{"description":"Resource specified in the request was not found.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"404 Not Found","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]}}},"paths":{"/rooms/{id}":{"get":{"tags":["rooms"],"summary":"Get room by ID","operationId":"getRoom","parameters":[{"name":"id","in":"path","description":"Room ID","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem401"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem404"}}}}}}}}}
```

## Delete room

> DESTRUCTIVE, CASCADING: deleting a room also cancels ALL of its reservations in the same moment — there is no option to keep them. Check GET /reservations?roomId={id} first and tell the user how many reservations will go, because that is a second, separate consequence they have to agree to. Reversible with POST /rooms/{id}/restore, which brings the room back together with exactly those reservations. 409 when the room is connected to a channel manager or to iCal.

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"rooms","description":"Create, read, update, and delete operations for rooms."}],"servers":[{"url":"https://api.mobile-calendar.com/v1/public","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"Pass the token in header: Authorization: Bearer <JWT>","bearerFormat":"JWT","scheme":"bearer"}},"schemas":{"ApiProblem401":{"description":"Missing authentication or invalid credentials.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"401 Unauthorized","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblemBase":{"description":"RFC 7807 Problem Details (base schema).","required":["type","title","status","detail","instance"],"properties":{"type":{"description":"RFC 7807 base — DO NOT use directly in endpoints.\nIn endpoints reference ApiProblem400/401/403/409/422/429/500.","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"},"ApiProblem404":{"description":"Resource specified in the request was not found.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"404 Not Found","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]}}},"paths":{"/rooms/{id}":{"delete":{"tags":["rooms"],"summary":"Delete room","description":"DESTRUCTIVE, CASCADING: deleting a room also cancels ALL of its reservations in the same moment — there is no option to keep them. Check GET /reservations?roomId={id} first and tell the user how many reservations will go, because that is a second, separate consequence they have to agree to. Reversible with POST /rooms/{id}/restore, which brings the room back together with exactly those reservations. 409 when the room is connected to a channel manager or to iCal.","operationId":"deleteRoom","parameters":[{"name":"id","in":"path","description":"Room ID","required":true,"schema":{"type":"integer"}}],"responses":{"204":{"description":"Deleted","headers":{"RUID":{"description":"Request unique identifier","required":true,"schema":{"type":"string"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem401"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem404"}}}}}}}}}
```

## PATCH /rooms/{id}

> Update room

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"rooms","description":"Create, read, update, and delete operations for rooms."}],"servers":[{"url":"https://api.mobile-calendar.com/v1/public","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"Pass the token in header: Authorization: Bearer <JWT>","bearerFormat":"JWT","scheme":"bearer"}},"schemas":{"RoomUpdateRequest":{"description":"Update room payload. True patch semantics: send only the fields you change — every field left out keeps its current value.","properties":{"name":{"description":"Room name shown in the calendar.","type":"string"},"persons":{"description":"Base number of persons (room capacity); must be >= maxAdults.","type":"integer"},"maxAdults":{"description":"Max adults the room hosts. Capacity rule: persons >= maxAdults > maxChildren (the room must fit at least one adult more than children — a child never stays without an accompanying adult).","type":"integer"},"maxChildren":{"description":"Max children. Must be STRICTLY LOWER than maxAdults (maxChildren < maxAdults): with maxAdults=4 the allowed range is 0-3. Use 0 for an adults-only room.","type":"integer"},"roomTypeId":{"description":"Assign the room to a room type. The type OVERWRITES capacity, beds, area, description and equipment with the values of the type.","type":"integer","nullable":true},"roomType":{"description":"Kind of unit (ROOM, APARTMENT, ...) — unrelated to `roomTypeId`, which points at a RoomTypes entity.","type":"string","nullable":true},"singleBed":{"type":"integer"},"doubleBed":{"type":"integer"},"extraBed":{"type":"integer"},"info":{"type":"string","nullable":true},"color":{"type":"string","nullable":true},"square":{"type":"string"},"squareType":{"type":"string"},"service":{"type":"string"},"serviceInfo":{"type":"string","nullable":true},"description":{"description":"Room description. A plain string changes only the default entry (`default`) and keeps the translations; an object `{default, <lang>…}` replaces the whole map (send all languages you want to keep).","oneOf":[{"type":"string"},{"type":"object","additionalProperties":{"type":"string"}}]},"equipment":{"type":"array","items":{"type":"string"}},"shareInOffer":{"type":"integer","enum":[0,1]},"locationId":{"type":"integer","nullable":true}},"type":"object"},"RoomResponse":{"description":"Single room response","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Room"},"meta":{"$ref":"#/components/schemas/MetaWithRuid"}},"type":"object"},"Room":{"description":"Public room representation","required":["roomId","name","persons","maxAdults","maxChildren","roomType","roomTypeId","singleBed","doubleBed","extraBed","service","shareInOffer"],"properties":{"roomId":{"type":"integer"},"name":{"type":"string"},"persons":{"description":"Base number of persons (room capacity); must be >= maxAdults.","type":"integer"},"maxAdults":{"description":"Max adults the room hosts. Capacity rule: persons >= maxAdults > maxChildren (the room must fit at least one adult more than children — a child never stays without an accompanying adult).","type":"integer"},"maxChildren":{"description":"Max children. Must be STRICTLY LOWER than maxAdults (maxChildren < maxAdults): with maxAdults=4 the allowed range is 0-3. Use 0 for an adults-only room; if the room really takes more children, raise maxAdults and persons.","type":"integer"},"roomType":{"description":"Enum (internal)","type":"string"},"roomTypeId":{"type":"integer","nullable":true},"singleBed":{"type":"integer"},"doubleBed":{"type":"integer"},"extraBed":{"type":"integer"},"service":{"description":"Service status enum","type":"string","enum":["CLEAN","DIRTY","WHILE_CLEANING"]},"serviceInfo":{"type":"string","nullable":true},"info":{"type":"string","nullable":true},"color":{"type":"string","nullable":true},"square":{"type":"string"},"squareType":{"type":"string"},"description":{"description":"Localized description map. Key 'default' is required. Other keys = ISO language codes.","type":"object","additionalProperties":{"type":"string"}},"equipment":{"type":"array","items":{"type":"string","enum":["AIR_CONDITIONING","KITCHENETTE","TELEVISION","BATHROOM","POOL","BALCONY","WASHING_MACHINE","KITCHEN_UTENSILS","BABY_CRIB","BLANKET","SCREEN","UMBRELLA","LOUNGER","CLOTHES_HANGER","DRYER","TOWELS","FLOOR_COVERING","FIREPLACE","HEATING","IRON","SAFE","DESK","BIDET","TOOTHBRUSH","SHAMPOO","HAIR_CONDITIONER","SOAP","RADIO","TABLE","WINE_GLASSES","CORKSCREW","WATER","GRILL","DISHWASHER","MINIBAR","REFRIGERATOR","COFFEE_MACHINE"]}},"shareInOffer":{"type":"integer","enum":[0,1]},"locationId":{"type":"integer","nullable":true}},"type":"object"},"MetaWithRuid":{"description":"Metadata with request tracking.","required":["ruid"],"properties":{"ruid":{"description":"Request Unique ID","type":"string","format":"uuid"}},"type":"object"},"ApiProblem400":{"description":"Request syntax/format error.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"400 Bad Request","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblemBase":{"description":"RFC 7807 Problem Details (base schema).","required":["type","title","status","detail","instance"],"properties":{"type":{"description":"RFC 7807 base — DO NOT use directly in endpoints.\nIn endpoints reference ApiProblem400/401/403/409/422/429/500.","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"},"ApiProblem401":{"description":"Missing authentication or invalid credentials.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"401 Unauthorized","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem404":{"description":"Resource specified in the request was not found.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"404 Not Found","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem422":{"description":"Input data validation error.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"422 Unprocessable Entity (validation)","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"},"errors":{"description":"Map: field name → error message(s)","type":"object","additionalProperties":{"$ref":"#/components/schemas/ErrorValue"}}},"type":"object"}]},"ErrorValue":{"description":"Single error message or list of messages.","oneOf":[{"description":"Error value in error map can be:\n- single string, or\n- array of strings (multiple messages for one field).","type":"string"},{"type":"array","items":{"type":"string"}}]}}},"paths":{"/rooms/{id}":{"patch":{"tags":["rooms"],"summary":"Update room","operationId":"updateRoom","parameters":[{"name":"id","in":"path","description":"Room ID","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomUpdateRequest"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomResponse"}}}},"400":{"description":"Invalid data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem400"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem401"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem404"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem422"}}}}}}}}}
```

## Restore a deleted room together with its reservations

> Reverses DELETE /rooms/{id}. Deleting a room cascades to all of its reservations (they are soft-deleted in the same moment); this endpoint brings the room back AND restores exactly those reservations — the ones cancelled together with the room (same deletion moment), not reservations cancelled earlier by hand. The room is re-activated at the end of the room order. Counts against the subscription room limit like creating a room. Returns the restored room plus the ids of the restored reservations. Use GET /reservations?showCancelled=1\&roomId= to preview what is currently cancelled for that room.

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"rooms","description":"Create, read, update, and delete operations for rooms."}],"servers":[{"url":"https://api.mobile-calendar.com/v1/public","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"Pass the token in header: Authorization: Bearer <JWT>","bearerFormat":"JWT","scheme":"bearer"}},"schemas":{"RoomRestoreResponse":{"description":"Restored room with the reservations brought back along with it","required":["data","meta"],"properties":{"data":{"required":["room","restoredReservations","restoredReservationIds"],"properties":{"room":{"$ref":"#/components/schemas/Room"},"restoredReservations":{"description":"Number of reservation rows restored together with the room","type":"integer"},"restoredReservationIds":{"type":"array","items":{"type":"integer"}}},"type":"object"},"meta":{"$ref":"#/components/schemas/MetaWithRuid"}},"type":"object"},"Room":{"description":"Public room representation","required":["roomId","name","persons","maxAdults","maxChildren","roomType","roomTypeId","singleBed","doubleBed","extraBed","service","shareInOffer"],"properties":{"roomId":{"type":"integer"},"name":{"type":"string"},"persons":{"description":"Base number of persons (room capacity); must be >= maxAdults.","type":"integer"},"maxAdults":{"description":"Max adults the room hosts. Capacity rule: persons >= maxAdults > maxChildren (the room must fit at least one adult more than children — a child never stays without an accompanying adult).","type":"integer"},"maxChildren":{"description":"Max children. Must be STRICTLY LOWER than maxAdults (maxChildren < maxAdults): with maxAdults=4 the allowed range is 0-3. Use 0 for an adults-only room; if the room really takes more children, raise maxAdults and persons.","type":"integer"},"roomType":{"description":"Enum (internal)","type":"string"},"roomTypeId":{"type":"integer","nullable":true},"singleBed":{"type":"integer"},"doubleBed":{"type":"integer"},"extraBed":{"type":"integer"},"service":{"description":"Service status enum","type":"string","enum":["CLEAN","DIRTY","WHILE_CLEANING"]},"serviceInfo":{"type":"string","nullable":true},"info":{"type":"string","nullable":true},"color":{"type":"string","nullable":true},"square":{"type":"string"},"squareType":{"type":"string"},"description":{"description":"Localized description map. Key 'default' is required. Other keys = ISO language codes.","type":"object","additionalProperties":{"type":"string"}},"equipment":{"type":"array","items":{"type":"string","enum":["AIR_CONDITIONING","KITCHENETTE","TELEVISION","BATHROOM","POOL","BALCONY","WASHING_MACHINE","KITCHEN_UTENSILS","BABY_CRIB","BLANKET","SCREEN","UMBRELLA","LOUNGER","CLOTHES_HANGER","DRYER","TOWELS","FLOOR_COVERING","FIREPLACE","HEATING","IRON","SAFE","DESK","BIDET","TOOTHBRUSH","SHAMPOO","HAIR_CONDITIONER","SOAP","RADIO","TABLE","WINE_GLASSES","CORKSCREW","WATER","GRILL","DISHWASHER","MINIBAR","REFRIGERATOR","COFFEE_MACHINE"]}},"shareInOffer":{"type":"integer","enum":[0,1]},"locationId":{"type":"integer","nullable":true}},"type":"object"},"MetaWithRuid":{"description":"Metadata with request tracking.","required":["ruid"],"properties":{"ruid":{"description":"Request Unique ID","type":"string","format":"uuid"}},"type":"object"},"ApiProblem401":{"description":"Missing authentication or invalid credentials.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"401 Unauthorized","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblemBase":{"description":"RFC 7807 Problem Details (base schema).","required":["type","title","status","detail","instance"],"properties":{"type":{"description":"RFC 7807 base — DO NOT use directly in endpoints.\nIn endpoints reference ApiProblem400/401/403/409/422/429/500.","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"},"ApiProblem403":{"description":"Authenticated user but no access.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"403 Forbidden","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem404":{"description":"Resource specified in the request was not found.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"404 Not Found","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]},"ApiProblem409":{"description":"Conflict with resource state or integrity constraints.","allOf":[{"$ref":"#/components/schemas/ApiProblemBase"},{"properties":{"type":{"description":"409 Conflict","type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"type":"object"}]}}},"paths":{"/rooms/{id}/restore":{"post":{"tags":["rooms"],"summary":"Restore a deleted room together with its reservations","description":"Reverses DELETE /rooms/{id}. Deleting a room cascades to all of its reservations (they are soft-deleted in the same moment); this endpoint brings the room back AND restores exactly those reservations — the ones cancelled together with the room (same deletion moment), not reservations cancelled earlier by hand. The room is re-activated at the end of the room order. Counts against the subscription room limit like creating a room. Returns the restored room plus the ids of the restored reservations. Use GET /reservations?showCancelled=1&roomId= to preview what is currently cancelled for that room.","operationId":"restoreRoom","parameters":[{"name":"id","in":"path","description":"Room ID (a deleted room — see the panel's room bin or a previous DELETE)","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Room restored","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomRestoreResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem401"}}}},"403":{"description":"Subscription room limit exceeded (code ROOM_LIMIT_EXCEED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem403"}}}},"404":{"description":"Room not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem404"}}}},"409":{"description":"Room is not deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem409"}}}}}}}}}
```
