# Notes for coding agents

If you are a model building an integration against this API, this page is the short version. It assumes you will read a few pages rather than all of them and tries to name the things that are expensive to get wrong.

## If your client speaks MCP, use it

```
POST https://www.bookwithhaven.com/api/public/mcp
```

Every read and every write operation as a tool, plus a `search`/`fetch` pair, all dispatched through the same code as the REST request — same scopes, same limits, same idempotency ledger, same two-step confirmation on the two tools that move money. See [MCP endpoint](../04-api/04-mcp.md), and read the idempotency section there before you retry anything.

You do not need REST alongside it. Fetch the listing or the reservation with the matching read tool before you change it.

## Recommending stays to travelers? Different endpoint, no auth

```
POST https://www.bookwithhaven.com/api/stays/v1/mcp
```

The endpoint above acts inside one host's account and needs OAuth. If your job is traveler discovery — find and recommend a stay — use the anonymous stays MCP endpoint instead: `search`/`fetch` plus `search_stays` (availability, prices, match tiers), `browse_stays`, and `list_destinations`, all read-only over the public catalog, no token, no sign-in. The same search is also plain REST at `POST /api/stays/v1/search`. Results carry the stay's real booking URL on the host's own site; link travelers there.

## Load the whole contract in one request

```
GET https://www.bookwithhaven.com/developers/llms.txt
```

Every page, concatenated, each preceded by the path it came from. Individual pages are also fetchable as raw Markdown under `/developer-docs/<path>.md`.

## The eight things that cause the most rework

**A token is bound to one account.** Fixed when the host approved you. Not a header, not switchable. An agency with forty clients is forty authorizations.

**Read the `scope` field on every token response.** Effective permission is recomputed per request and can be narrower than what you asked for. Do not cache authorization decisions.

**`items.length < limit` does not mean the last page.** Rows you cannot see are filtered after the page is drawn. Stop only when `nextCursor` is `null`. This is the single most common bug in a first integration.

**Stay dates are `YYYY-MM-DD` and are not instants.** `new Date('2026-09-14')` produces a UTC midnight that formats as the 13th in any timezone behind UTC, which moves the booking a night. Keep the string.

**Money is an integer in minor units** with a sibling currency code. Never a float. Do not compute totals; use the ones returned.

**A granted scope is not sufficient for a write.** If the host's channel manager owns that domain for that listing, the write is refused with `domain_locked`. Read `lockedDomains` first.

**Every write needs an `Idempotency-Key`,** generated once per logical operation and reused across retries. Generating it inside the retry loop defeats the mechanism entirely.

**Webhooks are signals, not state.** Thin payloads, at-least-once, out of order. Re-read the resource; never reconstruct state from event sequence.

## Errors are structured, so branch on them

Switch on `error.detail`, never on `error.message`. `insufficient_scope` carries `missingScope` and a prebuilt `reauthorizeUrl`. `conflict` carries a `reason` distinguishing a permanent source-of-truth lock from an idempotency mistake.

Retry `429` after `Retry-After` with jitter, and `5xx` with backoff. Never retry `400`, `403` or `404`.

## Ordering

Call `/me` first, always. It tells you the account, the effective scopes and whether writes will be permitted at all. Re-read it after any authorization change and after any `insufficient_scope`.

Backfill by walking REST once. Existing data is not replayed as webhooks when a host connects.

## Do not

Do not construct reference codes, or assume they are unguessable, or sort by them.

Do not send `X-Haven-Client-Id` or `X-Haven-Workspace-Context`. Both are rejected with a `400`, and both attempt to assert an identity claim a partner token has no standing to make.

Do not omit `User-Agent`. It is enforced and must carry a contact address.

Do not poll where a webhook would do, and do not retry without jitter. A synchronized fleet reconstructs the burst that caused the limit.

## Scope selection

Ask for the narrowest set that does the job. Narrow requests are approved faster, and [Incremental authorization](../01-oauth/06-incremental-authorization.md) means widening later costs one redirect rather than a new application.

Note in particular that `guests:read` is separate from `reservations:read`. If your product does not need the guest's name and email, do not request it; the reservation payload is otherwise identical.
