{
  "openapi": "3.1.0",
  "info": {
    "title": "Glanshjem AS — Mobile Car Wash Public API",
    "version": "1.0.0",
    "summary": "Public, agent-friendly endpoints used by the booking flow at https://www.glanshjem.no/bestill.",
    "description": "This document describes the existing public endpoints used by the booking UI. The recommended way for AI agents to make a booking today is to drive the booking form at /bestill (DOM contract documented in /llms.txt). A direct booking API is exposed but recommended only for verified integrations — see the Booking Flow notes below.\n\nNo authentication is required for the endpoints described here. All data is in Norwegian (`nb`) by default. All times are Europe/Oslo. All prices are NOK.",
    "contact": {
      "name": "Glanshjem AS",
      "email": "post@glanshjem.no",
      "url": "https://www.glanshjem.no"
    },
    "license": {
      "name": "Proprietary — Glanshjem AS",
      "identifier": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://www.glanshjem.no",
      "description": "Production"
    }
  ],
  "tags": [
    { "name": "Availability", "description": "Read calendar slots and admin-blocked dates." },
    { "name": "Discount",     "description": "Validate a discount code before applying it to a booking." },
    { "name": "Booking",      "description": "Create a booking. Use the form at /bestill for human flows; direct POST is for verified integrations." }
  ],
  "paths": {
    "/api/availability": {
      "get": {
        "tags": ["Availability"],
        "summary": "Get bookable slots and blocked times for a date range",
        "description": "Returns the global default time slots, day-specific extras (admin-added), and blocked combinations of (date, time_slot) — including both customer bookings and admin-blocked windows.",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": { "type": "string", "format": "date" },
            "example": "2026-05-01",
            "description": "Inclusive start of the window in YYYY-MM-DD (Europe/Oslo)."
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": { "type": "string", "format": "date" },
            "example": "2026-05-14",
            "description": "Inclusive end of the window in YYYY-MM-DD (Europe/Oslo)."
          }
        ],
        "responses": {
          "200": {
            "description": "Availability map.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "blocked": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "array",
                        "items": { "type": "string", "pattern": "^\\d{2}:\\d{2}$" }
                      },
                      "description": "Map from YYYY-MM-DD → list of HH:MM slots that are not bookable that day."
                    },
                    "slots": {
                      "type": "array",
                      "items": { "type": "string", "pattern": "^\\d{2}:\\d{2}$" },
                      "description": "Default time slots that apply to every day in the window. Default is 08:00–16:00 hourly."
                    },
                    "extras": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "array",
                        "items": { "type": "string", "pattern": "^\\d{2}:\\d{2}$" }
                      },
                      "description": "Day-specific slots admins added beyond the global defaults."
                    }
                  },
                  "required": ["blocked", "slots", "extras"]
                }
              }
            }
          },
          "400": { "description": "Missing or malformed `from`/`to`." }
        }
      }
    },
    "/api/discount": {
      "get": {
        "tags": ["Discount"],
        "summary": "Validate a discount code",
        "description": "Validates a discount code against the database. The check is read-only — it does not consume the code or modify state.",
        "parameters": [
          {
            "name": "code",
            "in": "query",
            "required": true,
            "schema": { "type": "string" },
            "example": "ÅPNING",
            "description": "Discount code, case-insensitive. The server upper-cases before lookup."
          }
        ],
        "responses": {
          "200": {
            "description": "Validation result. Note: HTTP 200 is returned even when `valid: false`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "valid":   { "type": "boolean" },
                    "pct":     { "type": "number", "description": "Percentage discount (0–100), present when type is 'percentage'." },
                    "fixed":   { "type": "number", "description": "Flat-NOK discount, present when type is 'fixed'." },
                    "label":   { "type": "string", "description": "Human-readable description of the discount." },
                    "message": { "type": "string", "description": "Reason the code was rejected, when valid is false." }
                  },
                  "required": ["valid"]
                }
              }
            }
          },
          "400": { "description": "Missing `code` parameter." }
        }
      }
    },
    "/api/bookings": {
      "post": {
        "tags": ["Booking"],
        "summary": "Create a booking",
        "description": "Creates a booking row, sends a confirmation email to the customer, and returns the booking ID. **For human flows, use the form at https://www.glanshjem.no/bestill — it handles validation, payment, and confirmation UI. Direct POST is intended for verified integrations only.**\n\nFor Vipps and card payments, use POST /api/vipps/initiate instead — that endpoint handles booking creation AND payment initiation in one call.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/BookingRequest" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Booking created.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BookingResponse" }
              }
            }
          },
          "400": { "description": "Validation error — missing fields, invalid service_id, or business-rule violation." }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "BookingRequest": {
        "type": "object",
        "required": [
          "service_id", "customer_name", "customer_phone", "customer_email",
          "address", "car_make", "car_model", "car_reg",
          "booking_date", "booking_time", "delivery_method", "payment_method"
        ],
        "properties": {
          "service_id":       { "type": "string", "format": "uuid", "description": "Service UUID. The booking page also accepts the human-readable slug in the ?service= URL param, but this endpoint requires the UUID." },
          "customer_name":    { "type": "string", "example": "Ola Nordmann" },
          "customer_phone":   { "type": "string", "example": "+4799999999", "description": "Norwegian mobile, 8 digits with optional country prefix." },
          "customer_email":   { "type": "string", "format": "email" },
          "address":          { "type": "string", "example": "Storgata 1, 3100 Tønsberg" },
          "car_make":         { "type": "string", "example": "Toyota" },
          "car_model":        { "type": "string", "example": "Corolla" },
          "car_reg":          { "type": "string", "example": "AB12345", "description": "Norwegian licence plate." },
          "booking_date":     { "type": "string", "format": "date", "example": "2026-05-10" },
          "booking_time":     { "type": "string", "pattern": "^\\d{2}:\\d{2}$", "example": "10:00" },
          "delivery_method":  { "type": "string", "enum": ["garage", "pickup"], "default": "garage", "description": "garage = we come to your address (default). pickup = we pick the car up and return it." },
          "payment_method":   { "type": "string", "enum": ["vipps", "card", "cash"], "description": "For 'vipps' or 'card', do NOT call this endpoint — use POST /api/vipps/initiate instead." },
          "discount_code":    { "type": "string", "nullable": true, "example": "ÅPNING" }
        }
      },
      "BookingResponse": {
        "type": "object",
        "required": ["id", "final_price", "status"],
        "properties": {
          "id":          { "type": "string", "format": "uuid" },
          "final_price": { "type": "number", "description": "Final amount in NOK after any discount." },
          "status":      { "type": "string", "enum": ["pending", "confirmed", "completed", "cancelled"] }
        }
      }
    }
  },
  "x-glanshjem-extras": {
    "service-catalog": {
      "description": "The full service catalog (id, name, price, slug) is exposed as Schema.org Offer items in the JSON-LD on the homepage <head>, and as a Markdown table in /llms.txt under 'Tjenester / Services'. There is no dedicated REST endpoint for this — the catalog is small and rarely changes.",
      "homepage_jsonld": "https://www.glanshjem.no",
      "llms_txt":        "https://www.glanshjem.no/llms.txt"
    },
    "agent-flow": {
      "description": "The recommended end-to-end flow for an AI agent is: (1) read /llms.txt for prices + DOM contract, (2) navigate to /bestill?service=<slug> with the desired service slug, (3) fill the form (field names documented in /llms.txt), (4) submit. After successful submit, the customer is shown a confirmation; the booking is also reachable at /bestill/success/{booking_id}.",
      "llms_txt": "https://www.glanshjem.no/llms.txt",
      "booking_page": "https://www.glanshjem.no/bestill",
      "confirmation_url_template": "https://www.glanshjem.no/bestill/success/{booking_id}",
      "schema_action": "ReserveAction declared on the LocalBusiness JSON-LD"
    },
    "rate-limit": {
      "description": "No explicit rate limiting today. Be polite — keep individual agent runs under ~1 request/second per endpoint. We may add limits in the future."
    },
    "no-auth": {
      "description": "All endpoints in this document are unauthenticated and intended for public use (the booking endpoint has business-rule validation but no auth). Admin endpoints under /api/admin/* require Supabase auth and are not documented here."
    }
  }
}
