# 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.

```json
{
  "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.
