Operations (writes)
In active development. This is the specification.
Every write is one endpoint.
POST /api/public/v1/operations/{operation}
Authorization: Bearer hvat_...
Idempotency-Key: <uuid>
Content-Type: application/json
User-Agent: YourProduct/1.2.0 (you@example.com)
The body is the operation's input. The response is the standard envelope.
Why one endpoint rather than REST verbs
Thirty write routes are thirty places to forget the scope check, the source-of-truth lock, the read-only backstop or the idempotency ledger. One route is one place, and the four checks run in a fixed order before any operation body executes.
It also means an operation name is a stable, machine-readable identifier for a capability, which is what lets the same catalog drive the REST surface and, later, an MCP server without a second implementation.
The catalog
Operation names are permanent once published. Each maps to the same internal action the Haven dashboard calls, so a write here fans out identically: channel manager push, guest notification, pricing sync.
Calendar
| Operation | Scope | Domain |
|---|---|---|
block_dates | calendar:write | AVAILABILITY |
unblock_dates | calendar:write | AVAILABILITY |
Rates
| Operation | Scope | Domain |
|---|---|---|
create_price_override | rates:write | RATES |
update_price_override | rates:write | RATES |
delete_price_override | rates:write | RATES |
clear_price_overrides_for_range | rates:write | RATES |
create_promotion | rates:write | RATES |
update_promotion | rates:write | RATES |
delete_promotion | rates:write | RATES |
delete_holiday_price_bump_rule | rates:write | RATES |
Stay rules
| Operation | Scope | Domain |
|---|---|---|
create_min_stay_override | stay-rules:write | BOOKING_RULES |
update_min_stay_override | stay-rules:write | BOOKING_RULES |
delete_min_stay_override | stay-rules:write | BOOKING_RULES |
create_weekly_min_stay_rule | stay-rules:write | BOOKING_RULES |
update_weekly_min_stay_rule | stay-rules:write | BOOKING_RULES |
delete_weekly_min_stay_rule | stay-rules:write | BOOKING_RULES |
sync_weekly_min_stay_rules | stay-rules:write | BOOKING_RULES |
set_check_in_out_blocks | stay-rules:write | BOOKING_RULES |
Listings
| Operation | Scope | Domain |
|---|---|---|
update_listing_content | listings:write | PROPERTY_CONTENT, LISTING_CONTENT |
set_listing_market_state | listings:publish | PROPERTY_CONTENT |
Reservations
| Operation | Scope | Confirmation |
|---|---|---|
approve_booking | reservations:write | |
respond_bundle_quote | reservations:write | |
cancel_reservation | reservations:cancel | |
issue_booking_refund | reservations:refund | required |
adjust_booking_price | reservations:refund | required |
Discounts
| Operation | Scope |
|---|---|
update_discount_code | discounts:write |
toggle_discount_code | discounts:write |
delete_discount_code | discounts:write |
Messaging
| Operation | Scope |
|---|---|
send_host_message | messaging:send |
mark_conversation_read | messaging:write |
Order of refusal
Checks run cheapest first, and a request that fails an earlier one never reaches a later one. This matters because it means a rejected write never touches the database and never consumes an idempotency record.
- Is the operation a real one? If not,
404. - Does your token carry the scope it declares? If not,
403withinsufficient_scope. - May the host's role write at all? If not,
403. - Does an external system own the domain for this listing? If so,
409withdomain_locked. - Claim the idempotency key.
- Run the operation.
Confirmation
Two operations move money and require an extra step: issue_booking_refund and adjust_booking_price. Call the operation with "confirm": false to receive a description of exactly what would happen and a single-use confirmation token, then call again with that token to commit.
A partner-initiated refund is the one class of write where a bug is unrecoverable, so it does not happen in one call.
Codes in, ids out
Operations take reference codes for top-level objects: propertyCode, reservationCode, conversationCode. Child rows are addressed by the integer id you read from the parent. Sending a raw internal id where a code is expected is refused rather than accepted.
Not available to any application
No operation reaches billing, subscriptions, payout destinations, the advertising wallet, team membership, workspace structure, or account deletion. These are not missing from the catalog; they are refused by scope, and the refusals are documented in the Scope reference.