Reference codes
Top-level objects are addressed by an eight-character lowercase hex string.
a1b2c3d4 a property
7f3e9c02 a reservation
b41c9e07 an account
They appear in every payload, every webhook and every URL, and they are stable for the life of the object. Store them as your foreign key to Haven.
Codes are not secrets
A code is a compact identifier, not a capability. Do not build anything on the assumption that one is hard to guess.
Authorization is always the account your token is bound to. A code belonging to another account returns 404, not 403, so the API cannot be used to test whether an object exists outside your grant.
Two consequences worth taking seriously. Do not put a code in a URL you would not want shared, treating it as an unguessable link. And do not use one as a shared secret between systems.
Child rows use integer ids
Objects that only exist inside a parent are addressed by integer id: a price override on a listing, a promotion, a min-stay rule.
{
"propertyCode": "a1b2c3d4",
"priceOverrides": [
{ "id": 88213, "date": "2026-09-14", "amountMinor": 42000 }
]
}
The parent's code carries the authorization, and every mutation on a child re-checks it. A child id from another listing does not resolve inside yours.
Two identifier vocabularies is a wart. It is the same wart the Haven mobile app carries, and the alternative, minting a code namespace for every child table, adds ceremony without adding a check.
Do not construct or parse them
Codes are derived, and Haven treats them as opaque strings. Sorting them is meaningless. Deriving one from another is not possible. Read them from responses; send them back verbatim.
Casing and validation
Always lowercase hex, always eight characters. Anything else is refused as invalid_input before the request reaches the database, so a malformed code costs you a fast 400 rather than a slow 404.