How it fits together
Haven's vocabulary differs from the platforms you have probably integrated with. Twenty minutes here will save you a redesign later.
Accounts, people and portfolios
A Haven account owns listings, reservations and guests. A person is a human with a login. Usually they are the same thing, but not always, and the difference matters to an API integration.
A person can act inside an account three ways. They may own it. They may hold a seat on someone else's account as a workspace member. Or they may manage it through an agency link, which is how a property manager operates a client's account without owning it.
Your token is bound to one (person, account) pair, fixed at the moment the host approved you. It is not a header you send and cannot be switched at request time. An agency managing forty client accounts and connecting your application to all of them produces forty separate authorizations, each one revocable by the host it belongs to and each one billed its own rate limit. That is a deliberate trade: a single portfolio-wide token would be more convenient for you and impossible for any individual host to revoke.
/me tells you which pair you are holding and what role it carries. A VIEWER role means every write will be refused no matter what scopes you hold, because a grant can never exceed the authority of the person who created it.
Listings
What Haven calls a property is what a guest would call a listing. Properties belong to an account, carry photos, capacity, location, check-in and check-out times, a base nightly rate and a currency.
A property may be governed by an external system. Hosts commonly run a channel manager, and when they do, that system owns some subset of the property's data. Haven models this as source-of-truth domains: availability, rates, fees, booking rules, listing content, property content, reservations and guest CRM. Each domain has an owner.
This is the single most important thing to understand about writing to Haven. A granted scope is necessary but not sufficient. If a host's channel manager owns the availability domain for a listing, your calendar:write scope is real, your token is valid, and the write is still refused, because accepting it would mean the next inbound sync silently overwrote you. Haven refuses these explicitly with a domain_locked error naming the domain and the system that owns it, rather than accepting a write that will not survive.
Every listing payload carries pmsOwner and lockedDomains so you can tell before you compose a write, and the planned integrations:read scope exposes a per-listing sync policy endpoint for the same purpose at sync time.
Calendars and reservations
A reservation is a stay. Haven's schema calls the underlying row an event, and you will occasionally see that word in error messages, but the API says reservation throughout.
A calendar span is either a reservation, a host block, or a reservation on a sibling listing in a linked calendar group. Linked calendars are how a host models a property that can be booked whole or by room; booking one locks the others. When you read a calendar you get all three kinds, with a reservation code where one exists so you can join a span to its booking.
Reservations carry an origin. A reservation that came from a channel partner cannot be cancelled through Haven, because Haven is not its system of record. Attempting it is refused rather than half-completed.
Guests
A guest is identified by email address rather than by a user row. Most guests never create a Haven login. A consequence worth designing around: the same person booking two stays at two different hosts is two independent guest records, and there is no cross-host guest identity to join on.
Guest names, email addresses and phone numbers are gated behind guests:read, separately from reservations:read. A token holding only reservations:read sees the stay, the dates and the money, with the guest fields nulled. That split exists so an integration that needs to know a property is occupied does not also receive the occupant's contact details.
Identifiers
Top-level objects are addressed by an eight-character hex reference code, not by a database id. Codes are stable and are what appears in every payload, every webhook and every error.
Codes are obfuscation, not authorization. Do not build anything on the assumption that a code is unguessable. Every request is authorized against the account your token is bound to, and a code belonging to another account returns 404 rather than 403, so you cannot use the API to discover whether an object exists outside your grant.
Child rows inside an already-authorized parent, such as a price override on a listing, are addressed by integer id. See Reference codes.
Reads and writes have different shapes
Reads are REST. GET /api/public/v1/properties, GET /api/public/v1/reservations/{code}, cursor paginated, filtered by query parameter.
Writes are not. Every write goes through POST /api/public/v1/operations/{operation} with a named operation and a JSON body. This is unusual and it is on purpose: one door means the scope check, the source-of-truth lock check, the read-only backstop and the idempotency ledger cannot be forgotten on one endpoint out of thirty. Each operation maps to the same internal action the Haven dashboard calls, which is why a write through the API notifies the guest, pushes to the channel manager and syncs pricing exactly as a write through the web app does.
Operations lists them.