{
  "openapi": "3.1.0",
  "info": {
    "title": "Dr Koło Bike Service API",
    "description": "API for checking availability, browsing services, booking appointments, and looking up appointment status at Dr Koło bicycle repair shop in Gdańsk, Poland.",
    "version": "1.0.0",
    "contact": {
      "name": "Dr Koło",
      "url": "https://drkolo.pl"
    }
  },
  "servers": [
    {
      "url": "https://iftyvvymlsdercmyagpe.supabase.co/functions/v1",
      "description": "Production"
    }
  ],
  "paths": {
    "/availability": {
      "get": {
        "operationId": "getAvailability",
        "summary": "Get available time slots for a date",
        "description": "Returns all available 30-minute appointment slots for the given date. Returns an empty slots array if the shop is closed that day.",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": true,
            "description": "Date in YYYY-MM-DD format",
            "schema": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$", "example": "2026-05-25" }
          }
        ],
        "responses": {
          "200": {
            "description": "Availability for the requested date",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AvailabilityResponse" },
                "examples": {
                  "open": {
                    "value": {
                      "date": "2026-05-25",
                      "open": "08:00",
                      "close": "17:00",
                      "slots": ["08:00", "08:30", "09:00"]
                    }
                  },
                  "closed": {
                    "value": { "date": "2026-05-24", "open": null, "close": null, "slots": [] }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid date parameter",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        },
        "security": [{ "anonKey": [] }]
      }
    },
    "/services": {
      "get": {
        "operationId": "getServices",
        "summary": "Get service catalog and pricing",
        "description": "Returns all available services grouped by category with prices in PLN.",
        "responses": {
          "200": {
            "description": "Service catalog",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ServicesResponse" }
              }
            }
          }
        },
        "security": [{ "anonKey": [] }]
      }
    },
    "/appointments": {
      "post": {
        "operationId": "createAppointment",
        "summary": "Book a service appointment",
        "description": "Creates an appointment inquiry. The shop will call the customer to confirm. Rate limits: max 3 attempts per phone per day, max 5 per IP per hour, max 50 AI bookings per day globally. Constraints: max 3 active bookings per phone, no duplicate same-phone+date, max 14 days in advance.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreateAppointmentRequest" },
              "example": {
                "date": "2026-05-25",
                "time": "10:00",
                "customer_name": "Jan Kowalski",
                "customer_phone": "+48600123456",
                "bike_manufacturer": "Trek",
                "bike_model": "Fuel EX 8",
                "service_note": "Serwis amortyzatora przedniego"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Appointment inquiry created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CreateAppointmentResponse" }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "409": {
            "description": "Time slot unavailable",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "429": {
            "description": "Rate limit exceeded (per-phone, per-IP, or global daily limit)",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        },
        "security": [{ "anonKey": [] }]
      },
      "get": {
        "operationId": "getAppointment",
        "summary": "Look up a single appointment by phone and lookup token",
        "description": "Returns the appointment matching the phone number and lookup token. The lookup_token is returned when creating a booking — store it to check status later.",
        "parameters": [
          {
            "name": "X-Customer-Phone",
            "in": "header",
            "required": true,
            "description": "Customer phone number (e.g. +48600123456)",
            "schema": { "type": "string", "example": "+48600123456" }
          },
          {
            "name": "X-Lookup-Token",
            "in": "header",
            "required": true,
            "description": "Lookup token returned from POST /appointments",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Appointment details",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AppointmentResponse" }
              }
            }
          },
          "400": {
            "description": "Missing phone or token header",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "404": {
            "description": "Appointment not found",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        },
        "security": [{ "anonKey": [] }]
      }
    }
  },
  "components": {
    "schemas": {
      "AvailabilityResponse": {
        "type": "object",
        "properties": {
          "date": { "type": "string" },
          "open": { "type": ["string", "null"], "description": "Shop opening time HH:MM or null if closed" },
          "close": { "type": ["string", "null"] },
          "slots": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Available 30-minute slots in HH:MM format"
          }
        }
      },
      "ServicesResponse": {
        "type": "object",
        "properties": {
          "categories": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": { "type": "string" },
                "services": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "name": { "type": "string" },
                      "price_pln": { "type": "integer" }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "CreateAppointmentRequest": {
        "type": "object",
        "required": ["date", "time", "customer_name", "customer_phone", "bike_manufacturer", "bike_model", "service_note"],
        "properties": {
          "date": { "type": "string", "description": "Appointment date YYYY-MM-DD" },
          "time": { "type": "string", "description": "Arrival time HH:MM (must be a slot from /availability)" },
          "customer_name": { "type": "string" },
          "customer_phone": { "type": "string", "description": "Customer phone — shop will call to confirm" },
          "bike_manufacturer": { "type": "string", "description": "Bike brand e.g. Trek, Specialized" },
          "bike_model": { "type": "string" },
          "service_note": { "type": "string", "description": "Description of what needs to be done" }
        }
      },
      "CreateAppointmentResponse": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "status": { "type": "string", "enum": ["zapytanie"] },
          "lookup_token": { "type": "string", "description": "Store this token to look up appointment status later via GET /appointments" },
          "message": { "type": "string" }
        }
      },
      "AppointmentResponse": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "date": { "type": "string" },
          "time": { "type": "string" },
          "status": {
            "type": "string",
            "enum": ["zapytanie", "potwierdzone", "odrzucone", "zakonczone"],
            "description": "zapytanie=inquiry, potwierdzone=confirmed, odrzucone=rejected, zakonczone=completed"
          },
          "bike_manufacturer": { "type": "string" },
          "bike_model": { "type": "string" },
          "service_note": { "type": ["string", "null"] },
          "created_at": { "type": "string" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "code": { "type": "string" }
        }
      }
    },
    "securitySchemes": {
      "anonKey": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "Supabase anon key. Send as: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImlmdHl2dnltbHNkZXJjbXlhZ3BlIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzgyMjE4MDUsImV4cCI6MjA5Mzc5NzgwNX0.TC2OIWB_NQVXEJXdp3XiIYTAJ3zMhfvioD-_9DDjUUY"
      }
    }
  }
}
