# Quickstart

The shortest path from an approved client to an authenticated call. Everything here works against sandbox credentials, so you can complete it before Haven has reviewed you for production.

> The resource endpoints and the authorization server are in active development. This page describes the contract they implement. Until they ship, treat it as the specification you are building against rather than a live tutorial.

## What you need

Your `client_id`, which looks like `hvci_` followed by an opaque string, and your `client_secret`, which looks like `hvcs_...`. If your client is public (a native app, a CLI, an MCP server) you have no secret and authenticate with PKCE alone.

One registered redirect URI. For a first pass, `http://127.0.0.1:8976/callback` is fine; loopback URIs may use any port at request time, so you do not have to register the port your server happens to bind.

## 1. Send the host to authorize

Generate a PKCE verifier and challenge. The verifier is 43 to 128 characters from the unreserved set; the challenge is its SHA-256 digest, base64url encoded without padding.

```bash
VERIFIER=$(openssl rand -base64 60 | tr -d '=+/' | cut -c1-64)
CHALLENGE=$(printf '%s' "$VERIFIER" | openssl dgst -binary -sha256 | openssl base64 | tr '+/' '-_' | tr -d '=')
```

Then build the authorization URL and open it in the host's browser.

```
https://www.bookwithhaven.com/oauth/authorize
  ?response_type=code
  &client_id=hvci_your_client
  &redirect_uri=http%3A%2F%2F127.0.0.1%3A8976%2Fcallback
  &scope=account%3Aread%20listings%3Aread%20calendar%3Aread%20calendar%3Awrite
  &state=<random, and check it on the way back>
  &code_challenge=$CHALLENGE
  &code_challenge_method=S256
```

Scopes are space separated. `S256` is the only accepted challenge method; `plain` is rejected. `state` is required.

The host sees what your application is, which of their accounts they are connecting, and a plain-language line for every scope you asked for. If they approve, Haven redirects to your `redirect_uri` with `code`, `state` and `iss`.

## 2. Exchange the code for tokens

Within sixty seconds, and only once.

```bash
curl -X POST https://www.bookwithhaven.com/api/public/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code="$CODE" \
  -d redirect_uri="http://127.0.0.1:8976/callback" \
  -d code_verifier="$VERIFIER"
```

```json
{
  "access_token": "hvat_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "hvrt_...",
  "scope": "account:read listings:read calendar:read calendar:write"
}
```

Read the `scope` field rather than assuming you received what you asked for. Haven returns it on every token response, including when it is unchanged, precisely so a narrowing is visible to you at the moment it happens.

## 3. Call the API

```bash
curl https://www.bookwithhaven.com/api/public/v1/me \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "User-Agent: YourProduct/1.2.0 (you@example.com)"
```

```json
{
  "success": true,
  "value": {
    "account": {
      "code": "b41c9e07",
      "displayName": "Cascade Rentals",
      "contextKind": "own-default",
      "role": "OWNER"
    },
    "application": {
      "clientId": "hvci_your_client",
      "name": "Your Product",
      "sandbox": false
    },
    "scopes": [
      "account:read",
      "listings:read",
      "calendar:read",
      "calendar:write"
    ],
    "capabilities": { "canWrite": true },
    "site": null
  },
  "error": null
}
```

`/me` is the call to make first in any integration. It tells you whose account you are holding, what you may do with it, and whether a write will succeed before you compose one.

The `User-Agent` is not optional. It must name your product and carry a contact address, so that when your integration is responsible for a traffic spike Haven can reach you rather than guess.

## 4. Make a write

Writes are a single endpoint. You name an operation and send its input.

```bash
curl -X POST https://www.bookwithhaven.com/api/public/v1/operations/block_dates \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -H "User-Agent: YourProduct/1.2.0 (you@example.com)" \
  -d '{"propertyCode":"a1b2c3d4","startDate":"2026-09-14","endDate":"2026-09-17","notes":"Owner stay"}'
```

The `Idempotency-Key` is required on every write. Retrying with the same key replays the stored result instead of blocking the dates twice. See [Idempotency](../03-conventions/06-idempotency.md).

## What to read next

[The authorization flow](../01-oauth/01-authorization-flow.md) covers refresh, rotation and revocation, all of which you need before you ship.

[Errors](../03-conventions/02-errors.md) is worth reading in full once. Haven's error bodies carry more than a message, and handling them properly removes most of the reasons you would otherwise write to support.
