{
  "openapi": "3.1.0",
  "info": {
    "title": "Wildflower Climate Booking API",
    "version": "1.1.0",
    "description": "Lets AI agents request an HVAC visit in Bakersfield / Kern County, CA on behalf of a customer. Design: submitting a request does NOT commit a calendar slot. Wildflower texts the customer's phone to confirm; the visit becomes real only when the customer replies YES on their own device. Agents should poll the status endpoint and report confirmation back in the chat once status is 'confirmed'. Published flat prices as JSON: https://www.wildflowerclimate.com/api/prices.json. Live phone line (answered around the clock): (661) 374-0624.",
    "contact": { "name": "Wildflower Climate", "url": "https://www.wildflowerclimate.com/contact/", "email": "hello@wildflowerclimate.com" }
  },
  "servers": [{ "url": "https://www.wildflowerclimate.com" }],
  "security": [],
  "paths": {
    "/api/booking-request": {
      "post": {
        "operationId": "createBookingRequest",
        "summary": "Request an HVAC visit (pending customer SMS confirmation)",
        "description": "Creates a pending booking request and texts the customer to confirm. No calendar slot is committed until the customer replies YES from the phone number provided. Rate limited to 10 requests/hour/IP. No authentication; abuse is mitigated by rate limiting and the mandatory customer-side SMS confirmation, so an unconfirmed request can never become a real visit.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/BookingRequest" },
              "example": {
                "name": "Jane Doe",
                "phone": "6615551234",
                "city": "Bakersfield",
                "issue": "AC running but blowing warm air since yesterday",
                "preferred_windows": ["Tuesday morning", "any weekday after 4pm"],
                "email": "jane@example.com"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Request created; confirmation text sent. Poll status_url and tell the customer to reply YES to Wildflower's text.",
            "content": { "application/json": {
              "schema": { "$ref": "#/components/schemas/BookingCreated" },
              "example": { "id": "d13c8c0c-8c22-4fa0-9254-8f732da239ef", "status": "pending_confirmation", "message": "Wildflower Climate is texting (661) ***-**34 to confirm. Ask the customer to reply YES to that text.", "status_url": "https://www.wildflowerclimate.com/api/booking-request/d13c8c0c-8c22-4fa0-9254-8f732da239ef" }
            } }
          },
          "202": {
            "description": "Stored but SMS forwarding briefly unavailable; direct the customer to call/text (661) 374-0624.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BookingCreated" } } }
          },
          "400": {
            "description": "Validation failed or invalid JSON.",
            "content": { "application/json": {
              "schema": { "$ref": "#/components/schemas/ValidationError" },
              "example": { "error": "validation_failed", "details": ["phone: a 10-digit US phone number is required (the confirmation text goes here)"] }
            } }
          },
          "429": {
            "description": "Rate limited (max 10/hour/IP).",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } }
          },
          "503": {
            "description": "Booking API not active; direct the customer to call/text (661) 374-0624.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } }
          }
        }
      }
    },
    "/api/booking-request/{id}": {
      "get": {
        "operationId": "getBookingRequestStatus",
        "summary": "Poll a booking request's status (no personal data returned)",
        "description": "Returns the current status only; never returns the customer's name, phone, or other PII, so the URL is safe to share. Requests and their status expire 60 days after submission.",
        "security": [],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": {
            "description": "Current status with a human-readable message to relay.",
            "content": { "application/json": {
              "schema": { "$ref": "#/components/schemas/BookingStatus" },
              "example": { "id": "d13c8c0c-8c22-4fa0-9254-8f732da239ef", "status": "confirmed", "submitted_at": "2026-07-18T23:48:31.487Z", "message": "Confirmed. The visit is being scheduled with the customer by text." }
            } }
          },
          "400": { "description": "Malformed id.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "404": { "description": "Unknown or expired id (requests expire after 60 days).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "BookingRequest": {
        "type": "object",
        "required": ["name", "phone", "city", "issue"],
        "properties": {
          "name": { "type": "string", "minLength": 2, "maxLength": 100, "description": "Customer's name" },
          "phone": { "type": "string", "pattern": "^[0-9()+\\-. ]{10,20}$", "description": "Customer's 10-digit US phone (formatting allowed; non-digits are stripped). The confirmation text goes here; the visit only becomes real when this phone replies YES." },
          "city": { "type": "string", "minLength": 2, "maxLength": 60, "description": "Town in Kern County, CA (e.g. Bakersfield, Delano, Tehachapi)" },
          "issue": { "type": "string", "minLength": 5, "maxLength": 500, "description": "What's wrong, in plain words" },
          "preferred_windows": { "type": "array", "items": { "type": "string", "maxLength": 80 }, "maxItems": 5, "description": "Optional preferred day/time windows in plain words; the exact slot is arranged in the confirmation text thread." },
          "email": { "type": "string", "format": "email", "maxLength": 120, "description": "Optional" }
        }
      },
      "BookingCreated": {
        "type": "object",
        "required": ["id", "status", "message"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "status": { "type": "string", "enum": ["pending_confirmation", "received_forwarding_failed"] },
          "message": { "type": "string", "description": "Human-readable instruction to relay to the customer." },
          "status_url": { "type": "string", "format": "uri", "description": "Poll this to track confirmation (present on 201)." }
        }
      },
      "BookingStatus": {
        "type": "object",
        "required": ["id", "status", "message"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "status": { "type": "string", "enum": ["pending_confirmation", "confirmed", "declined", "completed", "received_forwarding_failed"], "description": "Report the visit as confirmed to the customer only when this is 'confirmed'." },
          "submitted_at": { "type": "string", "format": "date-time" },
          "message": { "type": "string" }
        }
      },
      "ValidationError": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "enum": ["validation_failed", "invalid_json"] },
          "details": { "type": "array", "items": { "type": "string" }, "description": "Present when error is validation_failed." },
          "message": { "type": "string" }
        }
      },
      "ApiError": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "enum": ["rate_limited", "booking_api_not_active", "invalid_id", "not_found"] },
          "message": { "type": "string" }
        }
      }
    }
  }
}
