> 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/pricing.md).

# Pricing

Get pricing information for rooms or room types.

## Get pricing information for rooms/types and rates

> Returns daily prices and restrictions. Prices of derived (child) rates are always calculated dynamically from the parent rate (\`percentage\` = 100-based multiplier, \`additional\` = fixed amount) — also when filtering by the derived rate's \`rateId\` — and cannot be set directly via PUT /pricing. A derived rate inherits ONLY the price — its restrictions are exclusively its own (saved on the derived rate itself); the parent's restrictions never apply to it, and a day without own restrictions has none.

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"pricing","description":"Get pricing information for rooms or room types."}],"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":{"PricingResponse":{"description":"Pricing response for specified date range, rooms/types, and rates","required":["data","meta"],"properties":{"data":{"description":"Pricing data for all requested rooms/types and rates","type":"array","items":{"$ref":"#/components/schemas/PricingEntry"}},"meta":{"$ref":"#/components/schemas/MetaWithRuid"}},"type":"object"},"PricingEntry":{"description":"Pricing entry for a specific room or room type with rate","properties":{"roomId":{"description":"Room ID (null for room type pricing)","type":"integer","nullable":true},"typeId":{"description":"Room type ID (null for individual room pricing)","type":"integer","nullable":true},"lastEdit":{"description":"Last modification time of this room/type + rate pricing (UTC, ISO 8601) — same format as `editDate` of other resources.","type":"string","format":"date-time","nullable":true},"prices":{"description":"Daily prices for the requested date range","type":"array","items":{"$ref":"#/components/schemas/DailyPrice"}},"rate":{"$ref":"#/components/schemas/Rate"}},"type":"object"},"DailyPrice":{"description":"Price information for a specific date","required":["date","basePrice"],"properties":{"date":{"type":"string","format":"date"},"basePrice":{"description":"Base price for the leading occupancy","type":"number","format":"float"},"restrictions":{"oneOf":[{"$ref":"#/components/schemas/PriceRestrictions"}],"nullable":true,"description":"Booking restrictions for this date"},"derivedPrices":{"description":"Prices for different occupancy levels","type":"array","items":{"$ref":"#/components/schemas/OccupancyPrice"},"nullable":true}},"type":"object"},"PriceRestrictions":{"description":"Booking restrictions for a specific date","properties":{"minStay":{"description":"Minimum stay requirement in nights","type":"integer","nullable":true},"maxStay":{"description":"Maximum stay allowed in nights","type":"integer","nullable":true},"closed":{"description":"Whether this date is closed for bookings (0=open, 1=closed)","type":"integer"},"availArrival":{"description":"Whether arrival is allowed on this date (0=no, 1=yes)","type":"integer"},"availDeparture":{"description":"Whether departure is allowed on this date (0=no, 1=yes)","type":"integer"},"minAdvanceRes":{"description":"Minimum advance reservation days","type":"integer","nullable":true},"maxAdvanceRes":{"description":"Maximum advance reservation days","type":"integer","nullable":true}},"type":"object"},"OccupancyPrice":{"description":"Price for specific number of persons","required":["persons","price"],"properties":{"persons":{"description":"Number of persons","type":"integer"},"price":{"description":"Price for this occupancy level","type":"number","format":"float"}},"type":"object"},"Rate":{"description":"Rate information with cancellation policy","required":["rateId","name"],"properties":{"rateId":{"type":"integer"},"name":{"description":"Rate name","type":"string"},"parentRateId":{"description":"Parent rate ID for derived rates. Prices of a derived rate are calculated dynamically from the parent rate and cannot be set directly.","type":"integer","nullable":true},"percentage":{"description":"Derived rate: 100-based multiplier relative to the parent price (120 = 120% of parent, 85 = 85% of parent). Null for base rates.","type":"number","format":"float","nullable":true},"additional":{"description":"Derived rate: fixed amount added to the parent price (used instead of percentage). Null for base rates.","type":"number","format":"float","nullable":true},"cancellationPolicy":{"oneOf":[{"$ref":"#/components/schemas/CancellationPolicy"}],"nullable":true,"description":"Cancellation policy for this rate"}},"type":"object"},"CancellationPolicy":{"description":"Cancellation policy details","required":["cancellationId","type"],"properties":{"cancellationId":{"type":"integer"},"type":{"type":"string","enum":["REFUNDABLE","NONREFUNDABLE"]},"cancellationPenaltyPercentageAfterDeadline":{"description":"Penalty percentage after deadline","type":"integer","nullable":true},"deadlineDays":{"description":"Deadline in days before arrival","type":"integer","nullable":true},"deadlineHours":{"description":"Deadline in hours before arrival","type":"integer","nullable":true},"cancellationPenaltyNightsAfterDeadline":{"description":"Penalty in nights after deadline","type":"integer","nullable":true},"noShow":{"description":"No-show policy","type":"string"},"prepaymentPercentage":{"description":"Required prepayment percentage","type":"integer","nullable":true},"prepaymentFirstNight":{"description":"Whether prepayment covers first night only","type":"boolean"}},"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":{"/pricing":{"get":{"tags":["pricing"],"summary":"Get pricing information for rooms/types and rates","description":"Returns daily prices and restrictions. Prices of derived (child) rates are always calculated dynamically from the parent rate (`percentage` = 100-based multiplier, `additional` = fixed amount) — also when filtering by the derived rate's `rateId` — and cannot be set directly via PUT /pricing. A derived rate inherits ONLY the price — its restrictions are exclusively its own (saved on the derived rate itself); the parent's restrictions never apply to it, and a day without own restrictions has none.","operationId":"getPricing","parameters":[{"name":"from","in":"query","description":"Start date for pricing check (inclusive) - YYYY-MM-DD","required":true,"schema":{"type":"string","format":"date"}},{"name":"to","in":"query","description":"End date for pricing check (inclusive) - YYYY-MM-DD. Maximum 365 days from 'from' date.","required":true,"schema":{"type":"string","format":"date"}},{"name":"roomId","in":"query","description":"Array of room IDs to get pricing for. If not provided, returns pricing for all rooms.","required":false,"schema":{"type":"array","items":{"type":"integer"}}},{"name":"typeId","in":"query","description":"Array of room type IDs to get pricing for. If not provided, returns pricing for all room types.","required":false,"schema":{"type":"array","items":{"type":"integer"}}},{"name":"rateId","in":"query","description":"Array of rate IDs to get pricing for. If not provided, returns pricing for all rates.","required":false,"schema":{"type":"array","items":{"type":"integer"}}}],"responses":{"200":{"description":"Pricing data for requested rooms/types and rates","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PricingResponse"}}}},"400":{"description":"Bad request - invalid date range or parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem400"}}}},"401":{"description":"Unauthorized - invalid or missing authentication token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem401"}}}},"422":{"description":"Validation error - date range exceeds 365 days, invalid date format, or invalid room/type/rate IDs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem422"}}}}}}}}}
```

## Set prices and restrictions for rooms/types

> Sets base prices and/or booking restrictions for the given rate and rooms or room types. Works in range mode (\`startDate\` + \`stopDate\`, optionally limited to weekdays via \`days\`) or dates mode (explicit \`dates\` list; \`startDate\`/\`stopDate\`/\`days\` are then ignored). Existing values for the affected dates are overwritten; dates outside the request are untouched. Prices of derived (child) rates are recalculated automatically from the parent rate. OTA portals (Booking.com, Airbnb, Szállás) receive the new prices ONLY through active price sync rules (GET /channels/price-sync): every rule whose source is the saved rate × room/type pushes the price after its adjustment (e.g. +55 PLN, +10%) and rounding, asynchronously (usually within a minute); dates/units without an active rule are not pushed. The response carries \`priceSyncEventId\`.

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"pricing","description":"Get pricing information for rooms or room types."}],"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":{"SetPricingRequest":{"description":"Set prices and/or restrictions for rooms or room types in a date range or for an explicit list of dates","required":["rateId"],"properties":{"rateId":{"description":"Rate ID to set prices/restrictions for. For derived rates only restrictions can be set (price is calculated from the parent rate).","type":"integer"},"roomId":{"description":"Room IDs to set pricing for. Either `roomId` or `typeId` is required.","type":"array","items":{"type":"integer"},"nullable":true},"typeId":{"description":"Room type IDs to set pricing for. Either `roomId` or `typeId` is required.","type":"array","items":{"type":"integer"},"nullable":true},"startDate":{"description":"Range mode: first date to set (inclusive). Required unless `dates` is provided.","type":"string","format":"date","nullable":true},"stopDate":{"description":"Range mode: last date to set (inclusive). Max 731 days from `startDate`.","type":"string","format":"date","nullable":true},"days":{"description":"Range mode: limit changes to these weekdays (0=Sunday ... 6=Saturday). Default: all days.","type":"array","items":{"type":"integer","enum":[0,1,2,3,4,5,6]},"nullable":true},"dates":{"description":"Dates mode: explicit list of dates to set (max 366). When provided, `startDate`, `stopDate` and `days` are ignored.","type":"array","items":{"type":"string","format":"date"},"nullable":true},"basePrice":{"description":"Base price for the leading occupancy. `basePrice` and/or `restrictions` is required. A null value is ignored (prices stay unchanged); send 0 to clear the price. Maximum 1000000000 in any currency (a sanity bound against absurd values, with room for high-denomination currencies such as VND or IDR).","type":"number","format":"float","maximum":1000000000,"nullable":true},"restrictions":{"oneOf":[{"$ref":"#/components/schemas/SetPricingRestrictions"}],"nullable":true,"description":"Restrictions to set. `basePrice` and/or `restrictions` is required."}},"type":"object"},"SetPricingRestrictions":{"description":"Restrictions to set. Only provided keys are updated; omitted keys and keys with null values stay unchanged. To clear a numeric restriction send 0 (or `0D` for advance windows), to allow arrival/departure send true.","properties":{"minStay":{"description":"Minimum stay in nights (0 = no restriction)","type":"integer","minimum":0,"nullable":true},"maxStay":{"description":"Maximum stay in nights (0 = no restriction)","type":"integer","minimum":0,"nullable":true},"availArrival":{"description":"Whether arrival is allowed","type":"boolean","nullable":true},"availDeparture":{"description":"Whether departure is allowed","type":"boolean","nullable":true},"closed":{"description":"Whether the date is closed for bookings","type":"boolean","nullable":true},"minAdvanceRes":{"description":"Minimum advance reservation window, e.g. `1D` (one day), `12H` (12 hours), `1D12H`. `0D` = no restriction","type":"string","pattern":"^([0-9]{1,3}D)?([0-9]{1,2}H)?$","nullable":true},"maxAdvanceRes":{"description":"Maximum advance reservation window, e.g. `365D`. `0D` = no restriction","type":"string","pattern":"^([0-9]{1,3}D)?([0-9]{1,2}H)?$","nullable":true}},"type":"object"},"SetPricingResponse":{"description":"Response after setting prices/restrictions","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/SetPricingResult"},"meta":{"$ref":"#/components/schemas/MetaWithRuid"}},"type":"object"},"SetPricingResult":{"description":"Result of setting prices/restrictions","required":["createdPricingIds","updatedRows"],"properties":{"createdPricingIds":{"description":"IDs of newly created pricing records (one per room/type and year without existing pricing)","type":"array","items":{"type":"integer"}},"updatedRows":{"description":"Number of updated existing pricing records","type":"integer"},"priceSyncEventId":{"description":"ID of the emitted pricing.updated event. If any OTA price sync rules cover the saved rates, price propagation jobs are queued asynchronously from this event","type":"string","format":"uuid"}},"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":{"/pricing":{"put":{"tags":["pricing"],"summary":"Set prices and restrictions for rooms/types","description":"Sets base prices and/or booking restrictions for the given rate and rooms or room types. Works in range mode (`startDate` + `stopDate`, optionally limited to weekdays via `days`) or dates mode (explicit `dates` list; `startDate`/`stopDate`/`days` are then ignored). Existing values for the affected dates are overwritten; dates outside the request are untouched. Prices of derived (child) rates are recalculated automatically from the parent rate. OTA portals (Booking.com, Airbnb, Szállás) receive the new prices ONLY through active price sync rules (GET /channels/price-sync): every rule whose source is the saved rate × room/type pushes the price after its adjustment (e.g. +55 PLN, +10%) and rounding, asynchronously (usually within a minute); dates/units without an active rule are not pushed. The response carries `priceSyncEventId`.","operationId":"setPricing","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPricingRequest"}}}},"responses":{"200":{"description":"Prices/restrictions have been set","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPricingResponse"}}}},"400":{"description":"Bad request - malformed JSON body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem400"}}}},"401":{"description":"Unauthorized - invalid or missing authentication token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem401"}}}},"403":{"description":"Forbidden - token has no `pricing.update` scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem403"}}}},"404":{"description":"Rate, room or room type not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem404"}}}},"422":{"description":"Validation error - invalid dates, date range exceeds 731 days, price for derived rate, rate not assigned to room/type, or invalid restriction values. Also returned when the save changes minStay/maxStay (or minAdvanceRes/maxAdvanceRes) on a date and the result, counting values already saved for that date, is contradictory: `MIN_STAY_ABOVE_MAX_STAY` (minStay > maxStay) or `MIN_ADVANCE_ABOVE_MAX_ADVANCE` (minAdvanceRes > maxAdvanceRes); the error lists the affected dates and nothing is saved. Re-sending the values already stored for a date (e.g. price together with the unchanged restrictions) is never rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem422"}}}}}}}}}
```

## Quote the price of a stay (room or room type × rate × dates × guests)

> Returns the exact amount a reservation would cost in the mobile-calendar pricing for the given dates, unit and rate plan, calculated the same way as the reservation form in the panel: nightly prices of the rate (derived rates from the parent's price), the occupancy pricing model of the rate (price per number of persons — \`adults\` plus children priced as adults) and the children prices per age (\`childrenAges\`, one age per child; charged only when the rate has an occupancy pricing model — otherwise the price is per room and children are free). Send \`roomId\` (a room inside a room type is priced with the type's pricing) or \`roomTypeId\`, and optionally a \`rateId\` from GET /rates assigned to that room/type — omitted = the rate the panel selects by default (active rate named "Standard"), the same one createReservation uses when it prices a reservation automatically. Result: \`totalPrice\` (adults + children), \`adultsPrice\`, \`childrenPrice\`, per-night \`breakdown\`; when a night has no price or a child age has no configured price the quote is \`complete: false\` with \`issues\` and no total. \`occupancyPricing: false\` = price per room regardless of the number of guests (no extra-person charge). \`availability.available: false\` = the unit is occupied on these dates — tell the user, do not present the price as bookable. Restrictions (minimum stay, closed dates) are not checked — use GET /pricing. OTA portal prices are separate (GET /channels, price sync rules: GET /channels/price-sync). Required scope: \`pricing.read\`.

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"pricing","description":"Get pricing information for rooms or room types."}],"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":{"PriceQuoteResponse":{"required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/PriceQuote"},"meta":{"$ref":"#/components/schemas/MetaWithRuid"}},"type":"object"},"PriceQuote":{"description":"Quote for a stay in the mobile-calendar pricing, calculated exactly like the reservation form in the panel (and like the online booking engine for rates without an occupancy model). Only a `complete: true` quote has amounts; otherwise see `issues` (typically a night without a configured price). `availability` tells whether the unit is free on these dates — a quote for an occupied unit is NOT an offer (the booking engine would not show it). Restrictions (minimum stay, closed dates) are not checked — use GET /pricing.","required":["complete","totalPrice","adultsPrice","childrenPrice","averageNightPrice","nights","occupancy","occupancyPricing","breakdown","issues","roomId","roomName","roomTypeId","roomTypeName","pricedBy","capacity","rateId","rateName","parentRateId","arrival","departure","adults","childrenAges","currency"],"properties":{"complete":{"description":"Every night has a price and every child age has a configured children price.","type":"boolean"},"totalPrice":{"description":"Total for the whole stay (adults + children), account currency; null when incomplete.","type":"number","format":"float","nullable":true},"adultsPrice":{"description":"Sum of nightly unit prices for the adults (incl. children priced AS_ADULT).","type":"number","format":"float","nullable":true},"childrenPrice":{"description":"Sum of children extra charges (only rates with an occupancy pricing model; otherwise 0).","type":"number","format":"float","nullable":true},"averageNightPrice":{"type":"number","format":"float","nullable":true},"nights":{"type":"integer"},"occupancy":{"description":"Occupancy used by the pricing model = adults + children priced AS_ADULT.","type":"integer"},"occupancyPricing":{"description":"The rate has an occupancy pricing model for this unit (price depends on the number of persons, children are charged); false = price per room.","type":"boolean"},"breakdown":{"description":"Per-night prices.","type":"array","items":{"$ref":"#/components/schemas/PriceQuoteNight"}},"issues":{"type":"array","items":{"$ref":"#/components/schemas/PriceQuoteIssue"}},"roomId":{"type":"integer","nullable":true},"roomName":{"type":"string","nullable":true},"roomTypeId":{"description":"Room type used for pricing — set also when a `roomId` belonging to a room type was sent (rooms in a type share the type's pricing).","type":"integer","nullable":true},"roomTypeName":{"type":"string","nullable":true},"pricedBy":{"description":"Which pricing was used.","type":"string","enum":["room","roomType"]},"capacity":{"description":"Beds of the room / max persons of the type (informational — not enforced).","type":"integer","nullable":true},"rateId":{"type":"integer"},"rateName":{"type":"string"},"parentRateId":{"description":"Set for a derived rate (price calculated from the parent's price).","type":"integer","nullable":true},"arrival":{"type":"string","format":"date"},"departure":{"type":"string","format":"date"},"adults":{"type":"integer"},"childrenAges":{"type":"array","items":{"type":"integer"}},"currency":{"type":"string"},"rateDefaulted":{"description":"true = `rateId` was not sent and the rate the panel's reservation form selects by default was used (active rate named \"Standard\" assigned to the unit).","type":"boolean"},"availability":{"description":"Whether the unit is free for the whole stay (active reservations and date blocks), checked the same way the online booking engine selects offers. `available: false` = the room is occupied — say so instead of presenting the price as bookable.","properties":{"available":{"description":"roomId: the room is free every night; roomTypeId: at least one room of the type is free every night.","type":"boolean"},"roomIds":{"description":"Rooms checked.","type":"array","items":{"type":"integer"}},"availableRoomIds":{"description":"Rooms free for the whole stay.","type":"array","items":{"type":"integer"}}},"type":"object"}},"type":"object"},"PriceQuoteNight":{"required":["date","adultsPrice","childrenPrice","price"],"properties":{"date":{"type":"string","format":"date"},"adultsPrice":{"description":"Price of the unit for this night for the adults (after the occupancy pricing model); null = no price configured for this night.","type":"number","format":"float","nullable":true},"childrenPrice":{"description":"Extra charge for all children for this night (0 when the rate is priced per room).","type":"number","format":"float"},"price":{"description":"adultsPrice + childrenPrice; null when the night has no price.","type":"number","format":"float","nullable":true}},"type":"object"},"PriceQuoteIssue":{"description":"Why the quote is incomplete (blocking: PRICE_NOT_FOUND_FOR_DATE, CHILD_PRICE_NOT_CONFIGURED) or informational (CHILDREN_FREE_PER_ROOM_RATE: the rate has no occupancy pricing model, so the price is per room and children cost nothing).","required":["code","message"],"properties":{"code":{"type":"string","enum":["PRICE_NOT_FOUND_FOR_DATE","CHILD_PRICE_NOT_CONFIGURED","CHILDREN_FREE_PER_ROOM_RATE"]},"message":{"type":"string"},"date":{"description":"For PRICE_NOT_FOUND_FOR_DATE","type":"string","format":"date"},"age":{"description":"For CHILD_PRICE_NOT_CONFIGURED","type":"integer"}},"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"}]},"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":{"/price-quote":{"get":{"tags":["pricing"],"summary":"Quote the price of a stay (room or room type × rate × dates × guests)","description":"Returns the exact amount a reservation would cost in the mobile-calendar pricing for the given dates, unit and rate plan, calculated the same way as the reservation form in the panel: nightly prices of the rate (derived rates from the parent's price), the occupancy pricing model of the rate (price per number of persons — `adults` plus children priced as adults) and the children prices per age (`childrenAges`, one age per child; charged only when the rate has an occupancy pricing model — otherwise the price is per room and children are free). Send `roomId` (a room inside a room type is priced with the type's pricing) or `roomTypeId`, and optionally a `rateId` from GET /rates assigned to that room/type — omitted = the rate the panel selects by default (active rate named \"Standard\"), the same one createReservation uses when it prices a reservation automatically. Result: `totalPrice` (adults + children), `adultsPrice`, `childrenPrice`, per-night `breakdown`; when a night has no price or a child age has no configured price the quote is `complete: false` with `issues` and no total. `occupancyPricing: false` = price per room regardless of the number of guests (no extra-person charge). `availability.available: false` = the unit is occupied on these dates — tell the user, do not present the price as bookable. Restrictions (minimum stay, closed dates) are not checked — use GET /pricing. OTA portal prices are separate (GET /channels, price sync rules: GET /channels/price-sync). Required scope: `pricing.read`.","operationId":"getPriceQuote","parameters":[{"name":"arrival","in":"query","description":"Check-in date (first night)","required":true,"schema":{"type":"string","format":"date"}},{"name":"departure","in":"query","description":"Check-out date (exclusive); at least 1 and at most 90 nights after arrival","required":true,"schema":{"type":"string","format":"date"}},{"name":"roomId","in":"query","description":"Room ID (GET /rooms). Either roomId or roomTypeId is required, not both","required":false,"schema":{"type":"integer","minimum":1}},{"name":"roomTypeId","in":"query","description":"Room type ID (GET /room-types)","required":false,"schema":{"type":"integer","minimum":1}},{"name":"rateId","in":"query","description":"mobile-calendar rate plan ID (GET /rates) — must be assigned to the room/type. Omitted = the panel's default rate (active rate named \"Standard\"); 422 when the unit has none","required":false,"schema":{"type":"integer","minimum":1}},{"name":"adults","in":"query","description":"Number of adults (default 2)","required":false,"schema":{"type":"integer","default":2,"maximum":30,"minimum":1}},{"name":"childrenAges","in":"query","description":"Age of each child (0–17), one entry per child, e.g. `childrenAges[]=3&childrenAges[]=10`","required":false,"style":"form","explode":true,"schema":{"type":"array","items":{"type":"integer","maximum":17,"minimum":0},"maxItems":10}}],"responses":{"200":{"description":"Quote (check `complete` before using the amounts)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceQuoteResponse"}}}},"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, room type or rate not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem404"}}}},"422":{"description":"Validation error — invalid dates, both/neither of roomId/roomTypeId, rate not assigned to the room/type, invalid ages","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem422"}}}}}}}}}
```

## Get pricing model entries (price per room vs price per number of persons) for rooms/room types × rate plans

> Lists the pricing-model configuration of the mobile-calendar price list. Each entry binds one room OR room type to one rate plan and says how its calendar price is interpreted: \`PER\_ROOM\` — the price is for the whole room regardless of occupancy; \`PER\_PERSON\` — the calendar price applies to \`occupancyPricing.leadingOccupancy\` persons and other occupancies use the signed \`adjustments\` (PERCENT or AMOUNT per night; an occupancy without an adjustment falls back to the nearest configured one above it, occupancies above the leading one use the base price). IMPORTANT: pairs with NO entry use PER\_ROOM — the default. Filters: \`roomId\` (int or list), \`typeId\` (int or list), \`rateId\`; a filtered room that belongs to a room type returns the TYPE's entries (the type governs its price — see meta.roomsPricedByType). Children pricing is separate (rate plan children prices, see GET /price-quote). Required scope: \`pricing.read\`.

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"pricing","description":"Get pricing information for rooms or room types."}],"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":{"PricingModelListResponse":{"required":["data"],"properties":{"data":{"required":["entries"],"properties":{"entries":{"type":"array","items":{"$ref":"#/components/schemas/PricingModelEntry"}}},"type":"object"},"meta":{"properties":{"total":{"type":"integer"},"defaultModel":{"description":"Model used by every (room/type × rate) pair that has no entry in `entries`.","type":"string"},"roomsPricedByType":{"description":"Present when a roomId filter pointed at rooms assigned to a room type: such rooms are priced by their TYPE, so the type's entries are returned and this map shows which type governs which room.","type":"array","items":{"properties":{"roomId":{"type":"integer"},"typeId":{"type":"integer"}},"type":"object"}}},"type":"object"}},"type":"object"},"PricingModelEntry":{"description":"Pricing model of one (room OR room type) × rate plan pair. Exactly one of roomId/typeId is set. Pairs that have no entry at all use PER_ROOM (the default).","required":["rateId","roomId","typeId","model","occupancyPricing","editDate"],"properties":{"rateId":{"description":"Rate plan (GET /rates).","type":"integer"},"roomId":{"description":"Room (GET /rooms); null for a room-type entry.","type":"integer","nullable":true},"typeId":{"description":"Room type (GET /room-types); null for a room entry.","type":"integer","nullable":true},"model":{"description":"PER_ROOM = the calendar price is the price of the whole room regardless of occupancy; PER_PERSON = price depends on the number of persons (see occupancyPricing).","type":"string","enum":["PER_ROOM","PER_PERSON"]},"occupancyPricing":{"oneOf":[{"$ref":"#/components/schemas/OccupancyPricing"}],"nullable":true,"description":"Present only for model PER_PERSON; null for PER_ROOM."},"editDate":{"type":"string","format":"date-time","nullable":true}},"type":"object"},"OccupancyPricing":{"description":"Occupancy pricing configuration (model PER_PERSON). The calendar price is the price for `leadingOccupancy` persons; every other occupancy uses its adjustment. An occupancy WITHOUT an adjustment falls back to the nearest configured adjustment ABOVE it (towards leadingOccupancy); occupancies above leadingOccupancy without an adjustment use the base price. This is exactly how the panel and GET /price-quote compute prices.","required":["leadingOccupancy","adjustments"],"properties":{"leadingOccupancy":{"description":"Occupancy whose price equals the base (calendar) price.","type":"integer","minimum":1},"adjustments":{"type":"array","items":{"$ref":"#/components/schemas/OccupancyAdjustment"},"minItems":1}},"type":"object"},"OccupancyAdjustment":{"description":"Price adjustment for one occupancy level, relative to the base price (the price entered in the calendar, which applies to `leadingOccupancy`). Sign of `change` carries the direction: negative = cheaper than base, positive = more expensive.","required":["occupancy","change","unit"],"properties":{"occupancy":{"description":"Number of persons this adjustment applies to. Must differ from leadingOccupancy and must not exceed the capacity of the targeted room/room type.","type":"integer","minimum":1},"change":{"description":"Signed adjustment value (max 2 decimals). With unit PERCENT: -10 = 10% cheaper than base; a PERCENT decrease below -100 is rejected. With unit AMOUNT: value in the account currency per night.","type":"number","format":"float"},"unit":{"type":"string","enum":["PERCENT","AMOUNT"]}},"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"}]},"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":{"/pricing-model":{"get":{"tags":["pricing"],"summary":"Get pricing model entries (price per room vs price per number of persons) for rooms/room types × rate plans","description":"Lists the pricing-model configuration of the mobile-calendar price list. Each entry binds one room OR room type to one rate plan and says how its calendar price is interpreted: `PER_ROOM` — the price is for the whole room regardless of occupancy; `PER_PERSON` — the calendar price applies to `occupancyPricing.leadingOccupancy` persons and other occupancies use the signed `adjustments` (PERCENT or AMOUNT per night; an occupancy without an adjustment falls back to the nearest configured one above it, occupancies above the leading one use the base price). IMPORTANT: pairs with NO entry use PER_ROOM — the default. Filters: `roomId` (int or list), `typeId` (int or list), `rateId`; a filtered room that belongs to a room type returns the TYPE's entries (the type governs its price — see meta.roomsPricedByType). Children pricing is separate (rate plan children prices, see GET /price-quote). Required scope: `pricing.read`.","operationId":"getPricingModel","parameters":[{"name":"roomId","in":"query","description":"Room id or list of room ids (repeat the parameter for a list).","required":false,"schema":{"type":"integer"}},{"name":"typeId","in":"query","description":"Room type id or list of room type ids.","required":false,"schema":{"type":"integer"}},{"name":"rateId","in":"query","description":"Rate plan id.","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Pricing model entries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PricingModelListResponse"}}}},"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 / room type / rate not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem404"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem422"}}}}}}}}}
```

## Set the pricing model (price per room vs price per number of persons) for rooms/room types under a rate plan

> Sets how calendar prices of the listed rooms/room types are interpreted under one rate plan — the same operation as the panel's pricing-model dialog, including switching the per-person flag on existing calendar price rows. \`model: PER\_ROOM\` clears the occupancy configuration (prices become per room). \`model: PER\_PERSON\` requires \`occupancyPricing\`: the calendar price stays the price for \`leadingOccupancy\` persons and every listed adjustment changes it for another occupancy (\`change\` is signed: -10 with PERCENT = 10% cheaper; AMOUNT = currency per night; at least one adjustment required; occupancies must be unique, differ from leadingOccupancy and fit within each target's capacity). Rules: at least one of \`roomId\`/\`typeId\`; a room assigned to a room type is rejected with 422 — set the model on its typeId (the type governs the room's price); the rate plan must cover each target (\`rooms\`/\`types\` of GET /rates; empty = all). The change affects how prices are read everywhere (panel, GET /price-quote, booking engine) but does NOT push anything to OTA channels — OTA occupancy models are separate (see /channels). Required scope: \`pricing.update\`.

```json
{"openapi":"3.0.0","info":{"title":"Public REST API v1 – mobile-calendar.com","version":"1.0.0"},"tags":[{"name":"pricing","description":"Get pricing information for rooms or room types."}],"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":{"PricingModelPutRequest":{"description":"Sets the pricing model for every listed room and room type under one rate plan (same operation as the panel's pricing-model dialog). At least one of roomId/typeId is required. Rooms assigned to a room type are rejected — set the model on their typeId instead. The rate plan must cover each target (rate.rooms / rate.types; empty list or null = covers all).","required":["rateId","model"],"properties":{"rateId":{"description":"Rate plan (GET /rates).","type":"integer"},"roomId":{"description":"Room id or list of room ids (rooms WITHOUT a room type).","oneOf":[{"type":"integer"},{"type":"array","items":{"type":"integer"}}]},"typeId":{"description":"Room type id or list of room type ids.","oneOf":[{"type":"integer"},{"type":"array","items":{"type":"integer"}}]},"model":{"type":"string","enum":["PER_ROOM","PER_PERSON"]},"occupancyPricing":{"oneOf":[{"$ref":"#/components/schemas/OccupancyPricing"}],"nullable":true,"description":"Required for PER_PERSON (with at least one adjustment); must be omitted or null for PER_ROOM."}},"type":"object"},"OccupancyPricing":{"description":"Occupancy pricing configuration (model PER_PERSON). The calendar price is the price for `leadingOccupancy` persons; every other occupancy uses its adjustment. An occupancy WITHOUT an adjustment falls back to the nearest configured adjustment ABOVE it (towards leadingOccupancy); occupancies above leadingOccupancy without an adjustment use the base price. This is exactly how the panel and GET /price-quote compute prices.","required":["leadingOccupancy","adjustments"],"properties":{"leadingOccupancy":{"description":"Occupancy whose price equals the base (calendar) price.","type":"integer","minimum":1},"adjustments":{"type":"array","items":{"$ref":"#/components/schemas/OccupancyAdjustment"},"minItems":1}},"type":"object"},"OccupancyAdjustment":{"description":"Price adjustment for one occupancy level, relative to the base price (the price entered in the calendar, which applies to `leadingOccupancy`). Sign of `change` carries the direction: negative = cheaper than base, positive = more expensive.","required":["occupancy","change","unit"],"properties":{"occupancy":{"description":"Number of persons this adjustment applies to. Must differ from leadingOccupancy and must not exceed the capacity of the targeted room/room type.","type":"integer","minimum":1},"change":{"description":"Signed adjustment value (max 2 decimals). With unit PERCENT: -10 = 10% cheaper than base; a PERCENT decrease below -100 is rejected. With unit AMOUNT: value in the account currency per night.","type":"number","format":"float"},"unit":{"type":"string","enum":["PERCENT","AMOUNT"]}},"type":"object"},"PricingModelPutResponse":{"required":["data"],"properties":{"data":{"required":["entries"],"properties":{"entries":{"description":"Saved entries — one per targeted room/room type.","type":"array","items":{"$ref":"#/components/schemas/PricingModelEntry"}}},"type":"object"},"meta":{"properties":{"updatedPairs":{"type":"integer"},"pricingRowsFlagged":{"description":"How many calendar pricing rows had their per-person flag switched (informational).","type":"integer"}},"type":"object"}},"type":"object"},"PricingModelEntry":{"description":"Pricing model of one (room OR room type) × rate plan pair. Exactly one of roomId/typeId is set. Pairs that have no entry at all use PER_ROOM (the default).","required":["rateId","roomId","typeId","model","occupancyPricing","editDate"],"properties":{"rateId":{"description":"Rate plan (GET /rates).","type":"integer"},"roomId":{"description":"Room (GET /rooms); null for a room-type entry.","type":"integer","nullable":true},"typeId":{"description":"Room type (GET /room-types); null for a room entry.","type":"integer","nullable":true},"model":{"description":"PER_ROOM = the calendar price is the price of the whole room regardless of occupancy; PER_PERSON = price depends on the number of persons (see occupancyPricing).","type":"string","enum":["PER_ROOM","PER_PERSON"]},"occupancyPricing":{"oneOf":[{"$ref":"#/components/schemas/OccupancyPricing"}],"nullable":true,"description":"Present only for model PER_PERSON; null for PER_ROOM."},"editDate":{"type":"string","format":"date-time","nullable":true}},"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"}]},"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":{"/pricing-model":{"put":{"tags":["pricing"],"summary":"Set the pricing model (price per room vs price per number of persons) for rooms/room types under a rate plan","description":"Sets how calendar prices of the listed rooms/room types are interpreted under one rate plan — the same operation as the panel's pricing-model dialog, including switching the per-person flag on existing calendar price rows. `model: PER_ROOM` clears the occupancy configuration (prices become per room). `model: PER_PERSON` requires `occupancyPricing`: the calendar price stays the price for `leadingOccupancy` persons and every listed adjustment changes it for another occupancy (`change` is signed: -10 with PERCENT = 10% cheaper; AMOUNT = currency per night; at least one adjustment required; occupancies must be unique, differ from leadingOccupancy and fit within each target's capacity). Rules: at least one of `roomId`/`typeId`; a room assigned to a room type is rejected with 422 — set the model on its typeId (the type governs the room's price); the rate plan must cover each target (`rooms`/`types` of GET /rates; empty = all). The change affects how prices are read everywhere (panel, GET /price-quote, booking engine) but does NOT push anything to OTA channels — OTA occupancy models are separate (see /channels). Required scope: `pricing.update`.","operationId":"setPricingModel","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PricingModelPutRequest"}}}},"responses":{"200":{"description":"Model saved — returns the saved entries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PricingModelPutResponse"}}}},"400":{"description":"Malformed JSON body","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"}}}},"422":{"description":"Validation error (unknown fields, bad occupancy config, room priced by its type, rate not covering a target, capacity exceeded)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiProblem422"}}}}}}}}}
```
