# API Reference — Calendar Booking Platform

**Base URL:** `https://api.book.example.com/v1`  
**Format:** JSON  
**Authentication:** Bearer JWT for host endpoints; none for public booking endpoints

---

## Authentication

### Register

```http
POST /auth/register
Content-Type: application/json
```

**Request body:**

```json
{
  "email": "alice@example.com",
  "password": "securePassword123!",
  "fullName": "Alice Smith",
  "username": "alice-smith",
  "timezone": "America/New_York"
}
```

**Response `201 Created`:**

```json
{
  "id": "usr_01HXYZ...",
  "email": "alice@example.com",
  "fullName": "Alice Smith",
  "username": "alice-smith",
  "timezone": "America/New_York",
  "emailVerified": false
}
```

### Login

```http
POST /auth/login
Content-Type: application/json
```

**Request body:**

```json
{
  "email": "alice@example.com",
  "password": "securePassword123!"
}
```

**Response `200 OK`:**

```json
{
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "expiresIn": 3600,
  "tokenType": "Bearer",
  "user": {
    "id": "usr_01HXYZ...",
    "email": "alice@example.com",
    "username": "alice-smith",
    "timezone": "America/New_York"
  }
}
```

### Authenticated Requests

Include the token in all host endpoints:

```http
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```

---

## Profile

### Get Current User

```http
GET /me
Authorization: Bearer {token}
```

**Response `200 OK`:**

```json
{
  "id": "usr_01HXYZ...",
  "email": "alice@example.com",
  "fullName": "Alice Smith",
  "username": "alice-smith",
  "timezone": "America/New_York",
  "avatarUrl": null,
  "createdAt": "2026-06-01T10:00:00Z"
}
```

### Update Profile

```http
PATCH /me
Authorization: Bearer {token}
Content-Type: application/json
```

**Request body (all fields optional):**

```json
{
  "fullName": "Alice Smith",
  "timezone": "Europe/London",
  "avatarUrl": "https://cdn.example.com/avatars/alice.jpg"
}
```

---

## Availability

### Get Weekly Schedule

```http
GET /me/availability
Authorization: Bearer {token}
```

**Response `200 OK`:**

```json
{
  "timezone": "America/New_York",
  "minNoticeHours": 4,
  "maxAdvanceDays": 60,
  "weeklyRules": [
    { "dayOfWeek": 1, "startTime": "09:00", "endTime": "17:00" },
    { "dayOfWeek": 2, "startTime": "09:00", "endTime": "17:00" },
    { "dayOfWeek": 3, "startTime": "09:00", "endTime": "12:00" },
    { "dayOfWeek": 3, "startTime": "13:00", "endTime": "17:00" }
  ],
  "dateOverrides": [
    {
      "date": "2026-12-25",
      "available": false
    },
    {
      "date": "2026-07-04",
      "available": true,
      "intervals": [{ "startTime": "10:00", "endTime": "14:00" }]
    }
  ]
}
```

> `dayOfWeek`: 0 = Sunday, 1 = Monday, … 6 = Saturday.  
> Times are wall-clock in the host's timezone (`HH:mm` 24-hour format).

### Update Weekly Schedule

```http
PUT /me/availability
Authorization: Bearer {token}
Content-Type: application/json
```

**Request body:**

```json
{
  "minNoticeHours": 4,
  "maxAdvanceDays": 60,
  "weeklyRules": [
    { "dayOfWeek": 1, "startTime": "09:00", "endTime": "17:00" }
  ],
  "dateOverrides": []
}
```

**Response `200 OK`:** Same shape as GET.

---

## Event Types

### List Event Types

```http
GET /me/event-types
Authorization: Bearer {token}
```

**Response `200 OK`:**

```json
{
  "data": [
    {
      "id": "evt_01ABC...",
      "slug": "30min-intro",
      "title": "30 Minute Intro Call",
      "durationMinutes": 30,
      "bufferBeforeMinutes": 5,
      "bufferAfterMinutes": 5,
      "location": "Google Meet",
      "description": "A quick introduction call.",
      "customQuestions": [
        { "id": "q1", "label": "What would you like to discuss?", "required": true, "type": "text" }
      ],
      "isActive": true,
      "color": "#4F46E5",
      "publicUrl": "https://book.example.com/book/alice-smith/30min-intro",
      "createdAt": "2026-06-01T10:00:00Z"
    }
  ]
}
```

### Create Event Type

```http
POST /me/event-types
Authorization: Bearer {token}
Content-Type: application/json
```

**Request body:**

```json
{
  "slug": "30min-intro",
  "title": "30 Minute Intro Call",
  "durationMinutes": 30,
  "bufferBeforeMinutes": 5,
  "bufferAfterMinutes": 5,
  "location": "Google Meet",
  "description": "A quick introduction call.",
  "customQuestions": [],
  "color": "#4F46E5"
}
```

**Response `201 Created`:** Event type object.

### Update / Delete Event Type

```http
PATCH /me/event-types/{eventTypeId}
DELETE /me/event-types/{eventTypeId}
Authorization: Bearer {token}
```

Deleting soft-deletes (`isActive: false`) if bookings exist; hard-deletes if none.

---

## Calendar Connections

### List Connections

```http
GET /me/calendar-connections
Authorization: Bearer {token}
```

**Response `200 OK`:**

```json
{
  "data": [
    {
      "id": "cal_01DEF...",
      "provider": "google",
      "email": "alice@gmail.com",
      "calendars": [
        { "id": "primary", "name": "Alice Smith", "checkConflicts": true, "writeTarget": true },
        { "id": "abc123", "name": "Work", "checkConflicts": true, "writeTarget": false }
      ],
      "status": "connected",
      "lastSyncedAt": "2026-06-25T08:00:00Z"
    }
  ]
}
```

### Start OAuth Flow (Google / Microsoft)

```http
GET /me/calendar-connections/oauth/{provider}/authorize
Authorization: Bearer {token}
```

**Providers:** `google`, `microsoft`

**Response `302 Redirect`** to provider OAuth consent screen.

**Callback (handled by backend):**

```http
GET /oauth/callback/{provider}?code=...&state=...
```

Backend exchanges code for tokens, stores encrypted refresh token, redirects to dashboard.

### Connect Apple iCloud (CalDAV)

```http
POST /me/calendar-connections/apple
Authorization: Bearer {token}
Content-Type: application/json
```

**Request body:**

```json
{
  "appleId": "alice@icloud.com",
  "appSpecificPassword": "xxxx-xxxx-xxxx-xxxx"
}
```

**Response `201 Created`:** Calendar connection object.

### Update Calendar Selection

```http
PATCH /me/calendar-connections/{connectionId}
Authorization: Bearer {token}
Content-Type: application/json
```

**Request body:**

```json
{
  "calendars": [
    { "id": "primary", "checkConflicts": true, "writeTarget": true }
  ]
}
```

### Disconnect Calendar

```http
DELETE /me/calendar-connections/{connectionId}
Authorization: Bearer {token}
```

---

## Bookings (Host)

### List Bookings

```http
GET /me/bookings?status=confirmed&from=2026-06-01&to=2026-06-30
Authorization: Bearer {token}
```

**Query parameters:**

| Param | Type | Description |
|-------|------|-------------|
| `status` | string | `pending`, `confirmed`, `cancelled` |
| `from` | ISO date | Start of range |
| `to` | ISO date | End of range |
| `page` | int | Page number (default 1) |
| `limit` | int | Page size (default 20, max 100) |

**Response `200 OK`:**

```json
{
  "data": [
    {
      "id": "bkg_01GHI...",
      "eventType": { "id": "evt_01ABC...", "title": "30 Minute Intro Call" },
      "guestName": "Bob Jones",
      "guestEmail": "bob@example.com",
      "startTime": "2026-06-26T14:00:00Z",
      "endTime": "2026-06-26T14:30:00Z",
      "guestTimezone": "America/Los_Angeles",
      "status": "confirmed",
      "location": "Google Meet",
      "customAnswers": { "q1": "Product demo" },
      "createdAt": "2026-06-25T09:00:00Z"
    }
  ],
  "pagination": { "page": 1, "limit": 20, "total": 5 }
}
```

### Cancel Booking (Host)

```http
POST /me/bookings/{bookingId}/cancel
Authorization: Bearer {token}
Content-Type: application/json
```

**Request body:**

```json
{
  "reason": "Schedule conflict"
}
```

**Response `200 OK`:** Updated booking with `status: "cancelled"`.

### Reschedule Booking (Host)

```http
POST /me/bookings/{bookingId}/reschedule
Authorization: Bearer {token}
Content-Type: application/json
```

**Request body:**

```json
{
  "startTime": "2026-06-27T15:00:00Z"
}
```

---

## Public Endpoints (No Auth)

### Get Public Event Page Data

```http
GET /public/{username}/{eventSlug}
```

**Response `200 OK`:**

```json
{
  "host": {
    "fullName": "Alice Smith",
    "username": "alice-smith",
    "avatarUrl": null,
    "timezone": "America/New_York"
  },
  "eventType": {
    "slug": "30min-intro",
    "title": "30 Minute Intro Call",
    "durationMinutes": 30,
    "location": "Google Meet",
    "description": "A quick introduction call.",
    "customQuestions": [
      { "id": "q1", "label": "What would you like to discuss?", "required": true, "type": "text" }
    ]
  }
}
```

### Get Available Slots

```http
GET /public/{username}/{eventSlug}/slots?from=2026-06-26&to=2026-07-26&timezone=America/Los_Angeles
```

**Query parameters:**

| Param | Required | Description |
|-------|----------|-------------|
| `from` | Yes | Start date (`YYYY-MM-DD`) |
| `to` | Yes | End date (`YYYY-MM-DD`, max 31-day range) |
| `timezone` | Yes | IANA timezone for slot display |

**Response `200 OK`:**

```json
{
  "timezone": "America/Los_Angeles",
  "slots": [
    {
      "startTime": "2026-06-26T14:00:00Z",
      "endTime": "2026-06-26T14:30:00Z",
      "displayStart": "2026-06-26T07:00:00-07:00",
      "displayEnd": "2026-06-26T07:30:00-07:00"
    }
  ]
}
```

> Slots are returned in UTC (`startTime`/`endTime`) and in the requested timezone (`displayStart`/`displayEnd`).

### Create Booking

```http
POST /public/{username}/{eventSlug}/bookings
Content-Type: application/json
Idempotency-Key: {uuid}
```

**Request body:**

```json
{
  "startTime": "2026-06-26T14:00:00Z",
  "guestName": "Bob Jones",
  "guestEmail": "bob@example.com",
  "guestTimezone": "America/Los_Angeles",
  "customAnswers": {
    "q1": "Product demo"
  }
}
```

**Response `201 Created`:**

```json
{
  "id": "bkg_01GHI...",
  "status": "confirmed",
  "startTime": "2026-06-26T14:00:00Z",
  "endTime": "2026-06-26T14:30:00Z",
  "guestName": "Bob Jones",
  "guestEmail": "bob@example.com",
  "cancelUrl": "https://book.example.com/cancel/eyJhbG...",
  "confirmationMessage": "You're booked with Alice Smith!"
}
```

**Error `409 Conflict`** — slot no longer available:

```json
{
  "error": "slot_unavailable",
  "message": "This time slot is no longer available. Please choose another."
}
```

### Cancel Booking (Guest)

```http
POST /public/cancel
Content-Type: application/json
```

**Request body:**

```json
{
  "token": "eyJhbG..."
}
```

**Response `200 OK`:**

```json
{
  "id": "bkg_01GHI...",
  "status": "cancelled",
  "message": "Your booking has been cancelled."
}
```

---

## Webhooks (Future)

Host-configurable webhooks for events:

| Event | Payload |
|-------|---------|
| `booking.created` | Full booking object |
| `booking.cancelled` | Booking ID + cancelledBy |
| `booking.rescheduled` | Old and new times |

---

## Error Responses

All errors follow this format:

```json
{
  "error": "error_code",
  "message": "Human-readable description",
  "details": {}
}
```

| HTTP Status | Error Code | Description |
|-------------|------------|-------------|
| 400 | `validation_error` | Invalid request body |
| 401 | `unauthorized` | Missing or invalid token |
| 403 | `forbidden` | Not allowed for this resource |
| 404 | `not_found` | Resource does not exist |
| 409 | `slot_unavailable` | Booking conflict |
| 429 | `rate_limited` | Too many requests |
| 500 | `internal_error` | Server error |

---

## Rate Limits

| Endpoint group | Limit |
|----------------|-------|
| Public slots | 60 requests/min per IP |
| Public booking | 5 bookings/hour per guest email |
| Authenticated API | 300 requests/min per user |

Rate limit headers:

```http
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1719312000
```
