openapi: 3.1.0

info:
  title: CaddyBook API
  version: "1.1.0"
  summary: Search, check availability, and book verified golf caddies in Batam, Indonesia.
  description: |
    Reads are public. Booking writes require a partner Bearer API key with scopes
    `bookings:read` and/or `bookings:write`.

    There is no online payment. Tips are cash to the caddy. Booking fee is IDR 0.
    POST /bookings returns 202 with status pending_confirmation. Never report a
    caddy as secured until GET /bookings/{reference} returns confirmed.

    All tee times are Asia/Jakarta (UTC+7).

  contact:
    name: CaddyBook support
    email: hello@caddybook.asia
    url: https://www.caddybook.asia/en/contact

servers:
  - url: /v1
    description: Relative to the CaddyBook API host

tags:
  - name: Discovery
  - name: Booking

paths:
  /courses:
    get:
      tags: [Discovery]
      operationId: listCourses
      security: [{}]
      responses:
        "200":
          description: Course list

  /caddies:
    get:
      tags: [Discovery]
      operationId: searchCaddies
      security: [{}]
      parameters:
        - { name: course, in: query, schema: { type: string } }
        - { name: date, in: query, schema: { type: string, format: date } }
        - { name: tee_time, in: query, schema: { type: string, pattern: '^([01]\d|2[0-3]):[0-5]\d$' } }
        - { name: language, in: query, schema: { type: string } }
        - { name: experience, in: query, schema: { type: string, enum: [new, experienced, expert] } }
        - { name: specialty, in: query, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer, default: 20, maximum: 100 } }
      responses:
        "200":
          description: Matching caddies. is_available is null unless date+tee_time provided.

  /caddies/{id}:
    get:
      tags: [Discovery]
      operationId: getCaddy
      security: [{}]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string }, description: Caddy id or SEO slug }
      responses:
        "200": { description: Caddy }
        "404": { description: Not found }

  /caddies/{id}/availability:
    get:
      tags: [Discovery]
      operationId: getCaddyAvailability
      security: [{}]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string }, description: Caddy id or SEO slug }
        - { name: from, in: query, required: true, schema: { type: string, format: date } }
        - { name: to, in: query, required: true, schema: { type: string, format: date } }
      responses:
        "200":
          description: Open and booked slots from the default tee grid

  /tipping-guidelines:
    get:
      tags: [Discovery]
      operationId: getTippingGuidelines
      security: [{}]
      parameters:
        - { name: course, in: query, schema: { type: string } }
      responses:
        "200":
          description: Tip amounts in IDR

  /bookings:
    post:
      tags: [Booking]
      operationId: createBooking
      security:
        - bearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [caddy_id, date, tee_time, party_size, golfer]
              properties:
                caddy_id: { type: string }
                course: { type: string }
                date: { type: string, format: date }
                tee_time: { type: string }
                holes: { type: integer, enum: [9, 18] }
                party_size: { type: integer, minimum: 1, maximum: 2 }
                golfer:
                  type: object
                  required: [name, phone]
                  properties:
                    name: { type: string }
                    email: { type: string, format: email }
                    phone: { type: string }
                    preferred_language: { type: string }
                notes: { type: string }
                idempotency_key: { type: string, format: uuid }
      responses:
        "202":
          description: pending_confirmation
        "409":
          description: slot_unavailable with alternatives

  /bookings/{reference}:
    get:
      tags: [Booking]
      operationId: getBooking
      security:
        - bearerAuth: []
      parameters:
        - { name: reference, in: path, required: true, schema: { type: string } }
      responses:
        "200": { description: Booking status }

  /bookings/{reference}/cancel:
    post:
      tags: [Booking]
      operationId: cancelBooking
      security:
        - bearerAuth: []
      parameters:
        - { name: reference, in: path, required: true, schema: { type: string } }
      responses:
        "200": { description: Cancelled }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Partner API key (cb_live_…). Scopes bookings:read / bookings:write.
