# Bondin booking API

Use this if you are booking on someone's behalf. The HTML calendar is a client-side wizard; these endpoints are the source of truth.

Base URL: https://book.bondin.co

## 1. Catalog

`GET https://book.bondin.co/api/catalog`

Returns event types, hosts, availability windows, and the endpoint contract. CORS is open on this route.

## 2. Availability

`GET /api/slots?eventType=intro&tz=Europe/Belgrade`

Optional: `host=gv`, `with=lj,lt` (discovery only). `timezone` is an alias for `tz`.

```json
{
  "timezone": "Europe/Belgrade",
  "hostTimezone": "Europe/Belgrade",
  "mock": false,
  "book": { "method": "POST", "href": "/api/book" },
  "days": [
    {
      "date": "2026-09-14",
      "slots": [
        { "start": "2026-09-14T07:30:00.000Z", "end": "2026-09-14T07:50:00.000Z", "label": "9:30 AM" }
      ]
    }
  ]
}
```

Use `slots[].start` as-is in the next call.

## 3. Book

`POST /api/book`

Headers:

- `Content-Type: application/json`
- `Idempotency-Key: <8–128 chars>` — send this. Retry the same key after a timeout

```json
{
  "eventType": "intro",
  "start": "2026-09-14T07:30:00.000Z",
  "timezone": "Europe/Belgrade",
  "name": "Ada Lovelace",
  "email": "ada@example.com",
  "reason": "I want to talk through a product we are designing."
}
```

Optional: `host`, `with` (array of slugs, discover only).

Success (200):

```json
{
  "id": "…",
  "meetUrl": "https://meet.google.com/…",
  "start": "2026-09-14T07:30:00.000Z",
  "end": "2026-09-14T07:50:00.000Z",
  "timezone": "Europe/Belgrade",
  "eventType": "intro",
  "hostName": "George",
  "guestName": "Ada Lovelace",
  "mock": false
}
```

A confirmation also lives at `/booked/{id}`. Look up JSON at `GET /api/booking/{id}`.

## Errors

Every error is `{ "error": "human message", "code": "machine_code" }`.

| code | status | what to do |
| --- | --- | --- |
| missing_event_type | 400 | pass `eventType` |
| unknown_event_type | 400 | use a slug from the catalog |
| invalid_timezone | 400 | send a real IANA timezone |
| invalid_start / slot_taken | 400 / 409 | fetch slots again and pick a fresh `start` |
| validation | 400 | fix name, email, or reason |
| idempotency_conflict | 409 | this key was used for a different body — new key or same body |
| rate_limited | 429 | wait for `Retry-After` |
| quota_full / guest_limit | 429 | email hi@bondin.co |
| host_unavailable | 400 | that host does not offer the event |

## Limits

- 40 slot reads / IP / minute
- 5 bookings / IP / hour
- 3 upcoming bookings per guest email

Do not open this API from a random browser origin. Server-side and agent runtimes can call it directly. If you cannot finish, write to hi@bondin.co.
