{
  "openapi": "3.1.2",
  "info": {
    "title": "PolyJacks - Concrete Leveling & Polyjacking: Instant Price and Booking API",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://app.polyjacks.com/api/v1"
    },
    {
      "url": "https://app.polyjacks.com/sandbox/api/v1",
      "description": "Sandbox"
    }
  ],
  "paths": {
    "/get_requirements": {
      "get": {
        "operationId": "get_requirements",
        "summary": "Get requirements",
        "description": "Use this when you start helping someone with sunken or uneven concrete, before you ask them anything. It returns the questions to ask in order, the sentence you must read to the homeowner, the surfaces PolyJacks levels and the ones it does not, the photo shot list, and the service area. 1. Ask the questions one at a time. 2. Read the sentence and wait for a clear yes. 3. If you are unsure of the area, call check_service_area. 4. Call get_quote. For spray foam insulation, read the sentence, then call request_visit instead of get_quote.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Requirements"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict - see the problem code.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "410": {
            "description": "Gone - the quote expired.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Out of area.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Busy or a channel is offline.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/company_facts": {
      "get": {
        "operationId": "company_facts",
        "summary": "Company facts",
        "description": "Use this when the homeowner asks who PolyJacks is, how to reach a person, or what the warranty and the visit include. Every fact is a short sentence you may read aloud. 1. Read only facts from this response. 2. Never add a rating, award or promise that is not here.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompanyFacts"
                },
                "example": {
                  "name": "PolyJacks",
                  "website": "https://www.polyjacks.com",
                  "phone": "(913) 713-7794",
                  "email": "contact@polyjacks.com",
                  "facts": [
                    {
                      "key": "free_visit",
                      "text": "The on-site visit takes about 20 minutes, no obligation."
                    },
                    {
                      "key": "one_visit",
                      "text": "Most jobs are done in one visit, and you can drive on the surface the same day."
                    }
                  ],
                  "service_area": "Johnson County, KS and Jackson County, MO",
                  "sandbox": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict - see the problem code.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "410": {
            "description": "Gone - the quote expired.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Out of area.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Busy or a channel is offline.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/fit_check": {
      "post": {
        "operationId": "fit_check",
        "summary": "Fit check",
        "description": "Use this when you are not sure PolyJacks does the job. 1. Pass the surface key and whether the property is commercial. 2. fit instant: continue to get_quote. 3. fit visit: offer an on-site visit (request_visit, service concrete). 4. fit no: read why to the homeowner and stop - do not book.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FitCheck"
                },
                "example": {
                  "fit": "instant",
                  "why": "PolyJacks levels driveways, and this is a home.",
                  "next_step": "Collect the homeowner's details and call get_quote.",
                  "summary_for_user": "SANDBOX - not a real price: PolyJacks can level your driveway. Let's get your exact price next.",
                  "sandbox": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict - see the problem code.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "410": {
            "description": "Gone - the quote expired.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Out of area.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Busy or a channel is offline.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FitCheckRequest"
              }
            }
          }
        }
      }
    },
    "/check_service_area": {
      "post": {
        "operationId": "check_service_area",
        "summary": "Check service area",
        "description": "Use this when you need to know whether PolyJacks works at an address. 1. Prefer the full street address; a ZIP alone may answer partly. 2. in: continue. 3. partly or unknown: ask for the street address and call again. 4. out: the address is beyond the usual area, but PolyJacks still takes the request - continue to get_quote and tell the homeowner someone will be in touch if a time cannot be offered online. Never sign them up yourself.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AreaCheck"
                },
                "example": {
                  "area": "in",
                  "label": "Johnson County, KS",
                  "next_step": "Continue to get_quote.",
                  "summary_for_user": "SANDBOX: PolyJacks serves this area.",
                  "sandbox": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict - see the problem code.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "410": {
            "description": "Gone - the quote expired.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Out of area.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Busy or a channel is offline.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AreaRequest"
              }
            }
          }
        }
      }
    },
    "/get_quote": {
      "post": {
        "operationId": "get_quote",
        "summary": "Get quote",
        "description": "Use this when the homeowner wants their exact price. PolyJacks gives a price only with all four of the homeowner's details: full name, service address, mobile phone and email. 1. Read the sentence from get_requirements and get a clear yes before you send their details. 2. For each sunken area send the surface, the size (square feet, the length and width of the sunken part, or a size tile), how far it dropped at the joint in inches, and the gap under the edge in inches or null for don't know. 3. Call once with a new idempotency_key; after a network error retry with the SAME key. 4. If instant is false there is no instant price for this job: offer an on-site visit (list_windows, kind sales) and never guess a number. 5. Read summary_for_user to the homeowner word for word. 6. To change the areas, call again with quote_ref and a new key.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                },
                "example": {
                  "quote_ref": "q_SANDBOXsandboxSANDBOXa",
                  "version": 1,
                  "instant": false,
                  "reason": "commercial",
                  "valid_until": "2026-10-08",
                  "valid_until_label": "Thursday, October 8",
                  "priced_on": "2026-10-01",
                  "engine_version": "1.0.0",
                  "lock_rule": "Your price holds unless the site differs materially from what you showed us. If it does, you see the facts and the new price before any work, and you decide.",
                  "warranty": "5-year Stays Level limited warranty: if a lifted slab settles more than 1/2 inch within 5 years, your installer re-levels it at no charge under your agreement.",
                  "includes": [
                    "Foam lifting of the areas above",
                    "Drilling and patching the injection ports",
                    "Cleanup",
                    "5-year Stays Level limited warranty"
                  ],
                  "summary_for_user": "SANDBOX - not a real price: this one needs a quick look. An on-site visit gives the exact price.",
                  "next_steps": [
                    "list_windows"
                  ],
                  "sandbox": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict - see the problem code.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "410": {
            "description": "Gone - the quote expired.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Out of area.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Busy or a channel is offline.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteRequest"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "A fresh key for this request: 8-255 letters, digits, _ or -. Retry with the SAME key after a network error to get the same result back.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            }
          }
        ]
      }
    },
    "/request_visit": {
      "post": {
        "operationId": "request_visit",
        "summary": "Request a visit",
        "description": "Use this when the homeowner wants spray foam insulation, or fit_check says a concrete job needs a visit. 1. Read the sentence from get_requirements and get a clear yes before you send their details. 2. Send their full name, service address, mobile phone and email, and the service. 3. Keep visit_ref and pass it to list_windows and book_visit in place of a quote_ref. 4. There is no price from this tool, and you must never state one: the technician gives it at the visit. Nothing is booked until the homeowner answers the email PolyJacks sends them.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VisitRef"
                },
                "example": {
                  "visit_ref": "v_SANDBOXsandboxSANDBOXa",
                  "service": "spray_foam",
                  "summary_for_user": "SANDBOX - nothing was requested: next, offer visit windows.",
                  "next_steps": [
                    "list_windows",
                    "book_visit"
                  ],
                  "sandbox": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict - see the problem code.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "410": {
            "description": "Gone - the quote expired.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Out of area.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Busy or a channel is offline.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VisitRequest"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "A fresh key for this request: 8-255 letters, digits, _ or -. Retry with the SAME key after a network error to get the same result back.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            }
          }
        ]
      }
    },
    "/list_windows": {
      "post": {
        "operationId": "list_windows",
        "summary": "List windows",
        "description": "Use this when the homeowner wants a visit. 1. Tell them first: Please plan to be home - the technician will meet you there. Ask when they will be home and pass attend (any, weekday_am, weekday_pm or saturday). 2. Pass the quote_ref from get_quote (or the visit_ref from request_visit) and kind sales. Install days come only after the homeowner signs their own agreement. 3. Offer at most three windows, attendable ones first, by their label. 4. Windows go quickly: book soon after listing.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Windows"
                },
                "example": {
                  "windows": [
                    {
                      "window_id": "sales-20261002T1400Z",
                      "starts_at": "2026-10-02T14:00:00Z",
                      "ends_at": "2026-10-02T16:00:00Z",
                      "label": "Fri Oct 2, between 9:00 and 11:00 AM",
                      "attendable": true
                    }
                  ],
                  "summary_for_user": "SANDBOX: here are the next visit windows. They go quickly - book soon.",
                  "sandbox": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict - see the problem code.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "410": {
            "description": "Gone - the quote expired.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Out of area.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Busy or a channel is offline.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WindowsRequest"
              }
            }
          }
        }
      }
    },
    "/book_visit": {
      "post": {
        "operationId": "book_visit",
        "summary": "Book visit",
        "description": "Use this when the homeowner picked a window and said yes. 1. The homeowner must plan to be home - the technician meets them there; a visit is always booked that way. Send quote_ref (or visit_ref from request_visit), window_id, kind sales, homeowner_present true, and homeowner_informed true - meaning you told them PolyJacks will email them to confirm. 2. The visit is NOT booked until the homeowner answers Yes on the page linked in the email PolyJacks sends them, within 12 hours; tell them exactly that. 3. Keep booking_ref and call get_booking_status later. 4. You can never sign anything for the homeowner: agreements are signed only by the homeowner, on PolyJacks' own page.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                },
                "example": {
                  "booking_ref": "b_SANDBOXsandboxSANDBOaa",
                  "state": "pending_homeowner",
                  "kind": "sales",
                  "label": "Fri Oct 2, between 9:00 and 11:00 AM",
                  "hold_expires_at": "2026-10-02T14:30:00Z",
                  "confirm_via": [
                    "email"
                  ],
                  "summary_for_user": "SANDBOX - nothing was booked: ask the homeowner to open the email from PolyJacks and answer Yes.",
                  "next_steps": [
                    "get_booking_status"
                  ],
                  "sandbox": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict - see the problem code.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "410": {
            "description": "Gone - the quote expired.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Out of area.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Busy or a channel is offline.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingRequest"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "A fresh key for this request: 8-255 letters, digits, _ or -. Retry with the SAME key after a network error to get the same result back.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            }
          }
        ]
      }
    },
    "/get_booking_status": {
      "post": {
        "operationId": "get_booking_status",
        "summary": "Get booking status",
        "description": "Use this when you or the homeowner want to know whether a visit is confirmed. 1. Pass the booking_ref from book_visit. 2. Read summary_for_user. 3. expired means they did not confirm in time - you may book again.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookingStatus"
                },
                "example": {
                  "state": "confirmed",
                  "kind": "sales",
                  "label": "Fri Oct 2, between 9:00 and 11:00 AM",
                  "summary_for_user": "SANDBOX: this visit is confirmed.",
                  "sandbox": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict - see the problem code.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "410": {
            "description": "Gone - the quote expired.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Out of area.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Busy or a channel is offline.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingStatusRequest"
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Address": {
        "title": "Address",
        "description": "A US service address. PolyJacks levels concrete at homes in its service area only.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "street": {
            "type": "string",
            "description": "Street number and name, e.g. \"128 Sample Way\".",
            "minLength": 5,
            "maxLength": 120,
            "pattern": "^[A-Za-z0-9 .,'#/-]+$"
          },
          "unit": {
            "type": "string",
            "description": "Apartment, suite or unit (optional).",
            "maxLength": 20,
            "pattern": "^[A-Za-z0-9 .,'#/-]*$"
          },
          "city": {
            "type": "string",
            "minLength": 2,
            "maxLength": 60,
            "pattern": "^[A-Za-z0-9 .,'#/-]+$"
          },
          "state": {
            "type": "string",
            "description": "Two-letter US state or DC postal code.",
            "maxLength": 2,
            "pattern": "^[A-Z]{2}$"
          },
          "zip": {
            "type": "string",
            "description": "Five-digit US ZIP code.",
            "maxLength": 5,
            "pattern": "^[0-9]{5}$"
          }
        },
        "required": [
          "street",
          "city",
          "state",
          "zip"
        ]
      },
      "AreaCheck": {
        "title": "AreaCheck",
        "description": "Whether an address is inside PolyJacks's usual area. Every address can still be quoted and booked.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "area": {
            "type": "string",
            "enum": [
              "in",
              "partly",
              "out",
              "unknown"
            ]
          },
          "label": {
            "type": "string",
            "maxLength": 254
          },
          "next_step": {
            "type": "string",
            "maxLength": 254
          },
          "summary_for_user": {
            "type": "string",
            "maxLength": 1000
          },
          "sandbox": {
            "type": "boolean"
          }
        },
        "required": [
          "area",
          "label",
          "next_step",
          "summary_for_user"
        ],
        "examples": [
          {
            "area": "in",
            "label": "Johnson County, KS",
            "next_step": "Continue to get_quote.",
            "summary_for_user": "SANDBOX: PolyJacks serves this area.",
            "sandbox": true
          }
        ]
      },
      "AreaRequest": {
        "title": "AreaRequest",
        "description": "A ZIP code or a full address. Prefer the full street address; a ZIP alone may answer \"partly\".",
        "oneOf": [
          {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "zip": {
                "type": "string",
                "maxLength": 5,
                "pattern": "^[0-9]{5}$"
              }
            },
            "required": [
              "zip"
            ]
          },
          {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "address": {
                "$ref": "#/components/schemas/Address"
              }
            },
            "required": [
              "address"
            ]
          }
        ]
      },
      "Booking": {
        "title": "Booking",
        "description": "A held visit. It is NOT booked until the homeowner answers Yes to the email PolyJacks sends them, within 12 hours. webhook_secret is returned once (S2b).",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "booking_ref": {
            "type": "string",
            "maxLength": 24,
            "pattern": "^b_[A-Za-z0-9_-]{22}$"
          },
          "state": {
            "type": "string",
            "enum": [
              "pending_homeowner",
              "held",
              "confirmed",
              "expired",
              "cancelled",
              "rescheduled",
              "completed",
              "missed",
              "weather",
              "closed"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "sales",
              "rig"
            ]
          },
          "label": {
            "type": "string",
            "maxLength": 120
          },
          "hold_expires_at": {
            "type": "string",
            "maxLength": 40
          },
          "confirm_via": {
            "type": "array",
            "maxItems": 2,
            "items": {
              "type": "string",
              "enum": [
                "sms",
                "email"
              ]
            }
          },
          "summary_for_user": {
            "type": "string",
            "maxLength": 1000
          },
          "next_steps": {
            "type": "array",
            "maxItems": 8,
            "items": {
              "type": "string",
              "maxLength": 60,
              "pattern": "^[a-z0-9_]+$"
            }
          },
          "webhook_secret": {
            "type": "string",
            "maxLength": 254
          },
          "sandbox": {
            "type": "boolean"
          }
        },
        "required": [
          "booking_ref",
          "state",
          "kind",
          "label",
          "confirm_via",
          "summary_for_user",
          "next_steps"
        ],
        "examples": [
          {
            "booking_ref": "b_SANDBOXsandboxSANDBOaa",
            "state": "pending_homeowner",
            "kind": "sales",
            "label": "Fri Oct 2, between 9:00 and 11:00 AM",
            "hold_expires_at": "2026-10-02T14:30:00Z",
            "confirm_via": [
              "email"
            ],
            "summary_for_user": "SANDBOX - nothing was booked: ask the homeowner to open the email from PolyJacks and answer Yes.",
            "next_steps": [
              "get_booking_status"
            ],
            "sandbox": true
          }
        ]
      },
      "BookingRequest": {
        "title": "BookingRequest",
        "description": "Ask to hold a window the homeowner picked. Nothing is booked until the homeowner answers Yes to the email PolyJacks sends them, within 12 hours.",
        "oneOf": [
          {
            "type": "object",
            "description": "A quote's visit.",
            "additionalProperties": false,
            "properties": {
              "quote_ref": {
                "type": "string",
                "maxLength": 24,
                "pattern": "^q_[A-Za-z0-9_-]{22}$"
              },
              "window_id": {
                "type": "string",
                "maxLength": 32,
                "pattern": "^(sales|rig)-[0-9]{8}T[0-9]{4}Z$"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "sales",
                  "rig"
                ]
              },
              "homeowner_present": {
                "type": "boolean",
                "description": "The homeowner must plan to be home: the technician meets them there. A visit is always booked that way; false is accepted and ignored."
              },
              "homeowner_informed": {
                "const": true,
                "description": "You told the homeowner PolyJacks will email them to confirm."
              },
              "permission_ref": {
                "type": "string",
                "maxLength": 40,
                "pattern": "^[A-Za-z0-9_-]+$",
                "description": "Standing-permission ref (S2b)."
              },
              "callback_url": {
                "type": "string",
                "maxLength": 254,
                "description": "Webhook URL for booking updates (S2b)."
              }
            },
            "required": [
              "quote_ref",
              "window_id",
              "kind",
              "homeowner_present",
              "homeowner_informed"
            ]
          },
          {
            "type": "object",
            "description": "A visit request's visit.",
            "additionalProperties": false,
            "properties": {
              "window_id": {
                "type": "string",
                "maxLength": 32,
                "pattern": "^(sales|rig)-[0-9]{8}T[0-9]{4}Z$"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "sales",
                  "rig"
                ]
              },
              "homeowner_present": {
                "type": "boolean",
                "description": "The homeowner must plan to be home: the technician meets them there. A visit is always booked that way; false is accepted and ignored."
              },
              "homeowner_informed": {
                "const": true,
                "description": "You told the homeowner PolyJacks will email them to confirm."
              },
              "permission_ref": {
                "type": "string",
                "maxLength": 40,
                "pattern": "^[A-Za-z0-9_-]+$",
                "description": "Standing-permission ref (S2b)."
              },
              "callback_url": {
                "type": "string",
                "maxLength": 254,
                "description": "Webhook URL for booking updates (S2b)."
              },
              "visit_ref": {
                "type": "string",
                "maxLength": 24,
                "pattern": "^v_[A-Za-z0-9_-]{22}$",
                "description": "From request_visit, in place of quote_ref."
              }
            },
            "required": [
              "visit_ref",
              "window_id",
              "kind",
              "homeowner_present",
              "homeowner_informed"
            ]
          }
        ]
      },
      "BookingStatus": {
        "title": "BookingStatus",
        "description": "Whether a visit is confirmed. \"expired\" means the homeowner did not confirm in time - you may book again.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "pending_homeowner",
              "held",
              "confirmed",
              "expired",
              "cancelled",
              "rescheduled",
              "completed",
              "missed",
              "weather",
              "closed"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "sales",
              "rig"
            ]
          },
          "label": {
            "type": "string",
            "maxLength": 120
          },
          "summary_for_user": {
            "type": "string",
            "maxLength": 1000
          },
          "sandbox": {
            "type": "boolean"
          }
        },
        "required": [
          "state",
          "kind",
          "label",
          "summary_for_user"
        ],
        "examples": [
          {
            "state": "confirmed",
            "kind": "sales",
            "label": "Fri Oct 2, between 9:00 and 11:00 AM",
            "summary_for_user": "SANDBOX: this visit is confirmed.",
            "sandbox": true
          }
        ]
      },
      "BookingStatusRequest": {
        "title": "BookingStatusRequest",
        "description": "The booking_ref from book_visit.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "booking_ref": {
            "type": "string",
            "maxLength": 24,
            "pattern": "^b_[A-Za-z0-9_-]{22}$"
          }
        },
        "required": [
          "booking_ref"
        ]
      },
      "CompanyFacts": {
        "title": "CompanyFacts",
        "description": "Who PolyJacks is, how to reach a person, and what the warranty and the visit include. Every fact is a short sentence you may read aloud. Never add a rating, award or promise that is not here.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 120
          },
          "website": {
            "type": "string",
            "maxLength": 254
          },
          "phone": {
            "type": "string",
            "maxLength": 32
          },
          "email": {
            "type": "string",
            "maxLength": 254
          },
          "facts": {
            "type": "array",
            "maxItems": 40,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "key": {
                  "type": "string",
                  "maxLength": 60,
                  "pattern": "^[a-z0-9_]+$"
                },
                "text": {
                  "type": "string",
                  "maxLength": 500
                }
              },
              "required": [
                "key",
                "text"
              ]
            }
          },
          "service_area": {
            "type": "string",
            "maxLength": 254
          },
          "sandbox": {
            "type": "boolean"
          }
        },
        "required": [
          "name",
          "website",
          "phone",
          "email",
          "facts",
          "service_area"
        ],
        "examples": [
          {
            "name": "PolyJacks",
            "website": "https://www.polyjacks.com",
            "phone": "(913) 713-7794",
            "email": "contact@polyjacks.com",
            "facts": [
              {
                "key": "free_visit",
                "text": "The on-site visit takes about 20 minutes, no obligation."
              },
              {
                "key": "one_visit",
                "text": "Most jobs are done in one visit, and you can drive on the surface the same day."
              }
            ],
            "service_area": "Johnson County, KS and Jackson County, MO",
            "sandbox": true
          }
        ]
      },
      "Contact": {
        "title": "Contact",
        "description": "The homeowner's own details. PolyJacks prices only with all four: full name, service address, mobile phone and email. Never invent these - they must come from the homeowner.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "first_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40,
            "pattern": "^[A-Za-z .'-]+$"
          },
          "last_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 60,
            "pattern": "^[A-Za-z .'-]+$"
          },
          "phone": {
            "type": "string",
            "description": "The homeowner's own US mobile number. Toll-free and non-US numbers are refused.",
            "minLength": 7,
            "maxLength": 32,
            "pattern": "^[0-9 ()+.-]+$"
          },
          "email": {
            "type": "string",
            "description": "The homeowner's email.",
            "maxLength": 254,
            "pattern": "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,24}$"
          },
          "address": {
            "$ref": "#/components/schemas/Address"
          },
          "how_heard": {
            "type": "string",
            "description": "Optional. How the homeowner heard about PolyJacks.",
            "enum": [
              "google_search",
              "google_maps",
              "facebook",
              "referral",
              "repeat_customer",
              "ai_assistant",
              "yelp",
              "other"
            ]
          }
        },
        "required": [
          "first_name",
          "last_name",
          "phone",
          "email",
          "address"
        ]
      },
      "EmptyRequest": {
        "title": "EmptyRequest",
        "description": "This tool takes no arguments.",
        "type": "object",
        "additionalProperties": false,
        "properties": {}
      },
      "FitCheck": {
        "title": "FitCheck",
        "description": "Whether PolyJacks can help, and what to do next. Never a diagnosis.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "fit": {
            "type": "string",
            "enum": [
              "instant",
              "visit",
              "no"
            ]
          },
          "why": {
            "type": "string",
            "maxLength": 500
          },
          "next_step": {
            "type": "string",
            "maxLength": 254
          },
          "summary_for_user": {
            "type": "string",
            "maxLength": 1000
          },
          "sandbox": {
            "type": "boolean"
          }
        },
        "required": [
          "fit",
          "why",
          "next_step",
          "summary_for_user"
        ],
        "examples": [
          {
            "fit": "instant",
            "why": "PolyJacks levels driveways, and this is a home.",
            "next_step": "Collect the homeowner's details and call get_quote.",
            "summary_for_user": "SANDBOX - not a real price: PolyJacks can level your driveway. Let's get your exact price next.",
            "sandbox": true
          }
        ]
      },
      "FitCheckRequest": {
        "title": "FitCheckRequest",
        "description": "A surface and whether the property is commercial.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "surface": {
            "type": "string",
            "description": "A surface PolyJacks levels, or a surface it does not (from get_requirements).",
            "enum": [
              "driveway",
              "sidewalk",
              "patio",
              "garage_floor",
              "pool_deck",
              "porch_steps",
              "commercial_slab",
              "house_foundation_or_walls",
              "asphalt",
              "pavers_or_brick",
              "new_pour"
            ]
          },
          "commercial": {
            "type": "boolean",
            "description": "True for a business, rental or HOA property."
          }
        },
        "required": [
          "surface",
          "commercial"
        ]
      },
      "Problem": {
        "title": "Problem",
        "description": "An RFC 9457 problem document (application/problem+json). Every error returns one. Read fix and summary_for_user; retry only when retryable is true.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "type": {
            "type": "string",
            "maxLength": 254,
            "description": "A URL for this error code."
          },
          "title": {
            "type": "string",
            "maxLength": 120
          },
          "status": {
            "type": "integer",
            "minimum": 400,
            "maximum": 599
          },
          "detail": {
            "type": "string",
            "maxLength": 500
          },
          "instance": {
            "type": "string",
            "maxLength": 254,
            "description": "The route template that failed - never a value you sent."
          },
          "code": {
            "type": "string",
            "maxLength": 60,
            "pattern": "^[a-z_]+$"
          },
          "retryable": {
            "type": "boolean"
          },
          "fix": {
            "type": "string",
            "maxLength": 500,
            "description": "One sentence telling the assistant what to do next."
          },
          "summary_for_user": {
            "type": "string",
            "maxLength": 500
          },
          "retry_after_s": {
            "type": "integer",
            "minimum": 0,
            "maximum": 86400
          },
          "invalid_fields": {
            "type": "array",
            "maxItems": 64,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "pointer": {
                  "type": "string",
                  "maxLength": 254
                },
                "message": {
                  "type": "string",
                  "maxLength": 254
                }
              },
              "required": [
                "pointer",
                "message"
              ]
            }
          },
          "handoff_url": {
            "type": "string",
            "maxLength": 254
          },
          "upload_url": {
            "type": "string",
            "maxLength": 254
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "code",
          "retryable",
          "fix"
        ]
      },
      "Quote": {
        "title": "Quote",
        "description": "An exact price when instant is true; otherwise an on-site visit gives the exact price. Money fields are absent when instant is false. The price is before any sales tax. Never read a price to a homeowner before they gave their full contact.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "quote_ref": {
            "type": "string",
            "maxLength": 24,
            "pattern": "^q_[A-Za-z0-9_-]{22}$"
          },
          "version": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000000
          },
          "lock_id": {
            "type": "string",
            "maxLength": 32,
            "pattern": "^pl_[A-Za-z0-9]+$"
          },
          "instant": {
            "type": "boolean"
          },
          "reason": {
            "type": "string",
            "maxLength": 254,
            "description": "Why there is no instant price (when instant is false)."
          },
          "total_cents": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000000
          },
          "lines": {
            "type": "array",
            "maxItems": 8,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "area": {
                  "type": "string",
                  "maxLength": 2,
                  "pattern": "^s[1-8]$"
                },
                "surface": {
                  "type": "string",
                  "maxLength": 60
                },
                "area_label": {
                  "type": "string",
                  "maxLength": 120
                },
                "drop_label": {
                  "type": "string",
                  "maxLength": 120
                },
                "cents": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 100000000
                }
              },
              "required": [
                "area",
                "surface",
                "area_label",
                "drop_label"
              ]
            }
          },
          "caulk": {
            "type": "array",
            "maxItems": 8,
            "description": "Only on a quote priced before 2026-10-17: its joint caulking lines, part of total_cents. No new quote carries caulking - the price is for lifting the concrete only.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "area": {
                  "type": "string",
                  "maxLength": 60
                },
                "feet": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100000
                },
                "estimated": {
                  "type": "boolean"
                },
                "cents_per_ft": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 1000000
                },
                "removal": {
                  "type": "boolean"
                },
                "removal_cents_per_ft": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 1000000
                },
                "cents": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 100000000
                }
              },
              "required": [
                "area",
                "feet",
                "estimated",
                "removal"
              ]
            }
          },
          "caulk_minimum_cents": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000000,
            "description": "Only with caulk (a quote priced before 2026-10-17)."
          },
          "upgrades": {
            "type": "array",
            "maxItems": 2,
            "description": "The add-ons in the total: extra void fills (and crack sealing, only on a quote priced before 2026-10-17).",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "label": {
                  "type": "string",
                  "maxLength": 120
                },
                "cents": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 100000000
                }
              },
              "required": [
                "label",
                "cents"
              ]
            }
          },
          "valid_until": {
            "type": "string",
            "maxLength": 40
          },
          "valid_until_label": {
            "type": "string",
            "maxLength": 120
          },
          "priced_on": {
            "type": "string",
            "maxLength": 40
          },
          "engine_version": {
            "type": "string",
            "maxLength": 20,
            "description": "The pricing engine release that priced it (semver), or sandbox."
          },
          "lock_rule": {
            "type": "string",
            "maxLength": 500
          },
          "warranty": {
            "type": "string",
            "maxLength": 500
          },
          "includes": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string",
              "maxLength": 254
            }
          },
          "summary_for_user": {
            "type": "string",
            "maxLength": 1000
          },
          "next_steps": {
            "type": "array",
            "maxItems": 8,
            "items": {
              "type": "string",
              "maxLength": 60,
              "pattern": "^[a-z0-9_]+$"
            }
          },
          "sandbox": {
            "type": "boolean"
          }
        },
        "required": [
          "quote_ref",
          "version",
          "instant",
          "valid_until",
          "valid_until_label",
          "priced_on",
          "engine_version",
          "lock_rule",
          "warranty",
          "includes",
          "summary_for_user",
          "next_steps"
        ],
        "examples": [
          {
            "quote_ref": "q_SANDBOXsandboxSANDBOXa",
            "version": 1,
            "instant": false,
            "reason": "commercial",
            "valid_until": "2026-10-08",
            "valid_until_label": "Thursday, October 8",
            "priced_on": "2026-10-01",
            "engine_version": "1.0.0",
            "lock_rule": "Your price holds unless the site differs materially from what you showed us. If it does, you see the facts and the new price before any work, and you decide.",
            "warranty": "5-year Stays Level limited warranty: if a lifted slab settles more than 1/2 inch within 5 years, your installer re-levels it at no charge under your agreement.",
            "includes": [
              "Foam lifting of the areas above",
              "Drilling and patching the injection ports",
              "Cleanup",
              "5-year Stays Level limited warranty"
            ],
            "summary_for_user": "SANDBOX - not a real price: this one needs a quick look. An on-site visit gives the exact price.",
            "next_steps": [
              "list_windows"
            ],
            "sandbox": true
          }
        ]
      },
      "QuoteRequest": {
        "title": "QuoteRequest",
        "description": "Ask for an exact price. Send the homeowner's full contact for a new quote, OR a quote_ref to re-price an existing flow with new areas. Add an upgrade for extra voids. PolyJacks prices only with the homeowner's full name, service address, mobile phone and email.",
        "oneOf": [
          {
            "type": "object",
            "description": "A new quote: the homeowner's full contact and their sunken areas.",
            "additionalProperties": false,
            "properties": {
              "contact": {
                "$ref": "#/components/schemas/Contact"
              },
              "upgrades": {
                "type": "object",
                "description": "Optional add-on on top of the leveling - the same one the homeowner can add on polyjacks.com: voids = extra hollow spots to fill (0 to 20). A quantity only; the rate is PolyJacks' own. Zero clears it.",
                "additionalProperties": false,
                "properties": {
                  "voids": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 20
                  },
                  "crack_ft": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 0,
                    "description": "Deprecated (2026-10-17): crack sealing is no longer part of an online price - the technician can quote it on site. 0 is accepted and ignored; leave it out."
                  }
                },
                "required": [
                  "voids"
                ]
              },
              "surfaces": {
                "type": "array",
                "minItems": 1,
                "maxItems": 8,
                "items": {
                  "$ref": "#/components/schemas/SurfaceInput"
                }
              }
            },
            "required": [
              "contact",
              "surfaces"
            ]
          },
          {
            "type": "object",
            "description": "Re-price an existing flow: its quote_ref and the revised areas (contact already captured).",
            "additionalProperties": false,
            "properties": {
              "quote_ref": {
                "type": "string",
                "maxLength": 24,
                "pattern": "^q_[A-Za-z0-9_-]{22}$"
              },
              "upgrades": {
                "type": "object",
                "description": "Optional add-on on top of the leveling - the same one the homeowner can add on polyjacks.com: voids = extra hollow spots to fill (0 to 20). A quantity only; the rate is PolyJacks' own. Zero clears it.",
                "additionalProperties": false,
                "properties": {
                  "voids": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 20
                  },
                  "crack_ft": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 0,
                    "description": "Deprecated (2026-10-17): crack sealing is no longer part of an online price - the technician can quote it on site. 0 is accepted and ignored; leave it out."
                  }
                },
                "required": [
                  "voids"
                ]
              },
              "surfaces": {
                "type": "array",
                "minItems": 1,
                "maxItems": 8,
                "items": {
                  "$ref": "#/components/schemas/SurfaceInput"
                }
              }
            },
            "required": [
              "quote_ref",
              "surfaces"
            ]
          }
        ]
      },
      "Requirements": {
        "title": "Requirements",
        "description": "What to ask the homeowner, the sentence to read them, the surfaces PolyJacks levels, the photo shot list and the service area.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "questions": {
            "type": "array",
            "maxItems": 40,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "key": {
                  "type": "string",
                  "maxLength": 60,
                  "pattern": "^[a-z0-9_]+$"
                },
                "ask": {
                  "type": "string",
                  "maxLength": 254
                },
                "required": {
                  "type": "boolean"
                }
              },
              "required": [
                "key",
                "ask",
                "required"
              ]
            }
          },
          "consent_sentence": {
            "type": "string",
            "maxLength": 1000,
            "description": "Read this to the homeowner and wait for a clear yes before get_quote."
          },
          "surfaces": {
            "type": "array",
            "maxItems": 40,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "key": {
                  "type": "string",
                  "maxLength": 60,
                  "pattern": "^[a-z0-9_]+$"
                },
                "label": {
                  "type": "string",
                  "maxLength": 120
                },
                "presets": {
                  "type": "array",
                  "maxItems": 20,
                  "description": "Size tiles for this surface; send a tile's key as size.preset in get_quote.",
                  "items": {
                    "type": "object",
                    "additionalProperties": false,
                    "properties": {
                      "key": {
                        "type": "string",
                        "maxLength": 60,
                        "pattern": "^[a-z_]+:[0-9]+$"
                      },
                      "label": {
                        "type": "string",
                        "maxLength": 120
                      }
                    },
                    "required": [
                      "key",
                      "label"
                    ]
                  }
                },
                "instant": {
                  "type": "boolean",
                  "description": "Whether an instant price is possible for this surface."
                }
              },
              "required": [
                "key",
                "label",
                "presets",
                "instant"
              ]
            }
          },
          "not_done": {
            "type": "array",
            "maxItems": 40,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "key": {
                  "type": "string",
                  "maxLength": 60,
                  "pattern": "^[a-z0-9_]+$"
                },
                "why": {
                  "type": "string",
                  "maxLength": 254
                }
              },
              "required": [
                "key",
                "why"
              ]
            }
          },
          "photo_guide": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "slot": {
                  "type": "string",
                  "enum": [
                    "wide",
                    "drop",
                    "gap",
                    "extra"
                  ]
                },
                "ask": {
                  "type": "string",
                  "maxLength": 254
                }
              },
              "required": [
                "slot",
                "ask"
              ]
            }
          },
          "service_area": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "label": {
                "type": "string",
                "maxLength": 254
              },
              "counties": {
                "type": "array",
                "maxItems": 50,
                "items": {
                  "type": "string",
                  "maxLength": 120
                }
              }
            },
            "required": [
              "label",
              "counties"
            ]
          },
          "contact_rule": {
            "type": "string",
            "maxLength": 254
          },
          "next_steps": {
            "type": "array",
            "maxItems": 8,
            "items": {
              "type": "string",
              "maxLength": 60,
              "pattern": "^[a-z0-9_]+$"
            }
          },
          "sandbox": {
            "type": "boolean"
          },
          "visit_only_services": {
            "type": "array",
            "maxItems": 8,
            "description": "Services PolyJacks books as an on-site visit with no price from an assistant. Call request_visit for these.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "key": {
                  "type": "string",
                  "maxLength": 60,
                  "pattern": "^[a-z0-9_]+$"
                },
                "label": {
                  "type": "string",
                  "maxLength": 120
                }
              },
              "required": [
                "key",
                "label"
              ]
            }
          }
        },
        "required": [
          "questions",
          "consent_sentence",
          "surfaces",
          "not_done",
          "photo_guide",
          "service_area",
          "contact_rule",
          "next_steps"
        ]
      },
      "SurfaceInput": {
        "title": "SurfaceInput",
        "description": "One sunken concrete area. Give its surface, its size (the WHOLE slab that sank, joint to joint - we lift whole slabs, not strips - as its length and width, square feet, or a size tile) and how far each of its four corners sank (corners) - or one drop at the joint (drop_in). A corner deeper than 6 inches is measured in person: book a visit instead.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "surface": {
            "type": "string",
            "description": "Which concrete surface.",
            "enum": [
              "driveway",
              "sidewalk",
              "patio",
              "garage_floor",
              "pool_deck",
              "porch_steps",
              "commercial_slab"
            ]
          },
          "size": {
            "description": "The area's size, given one of three ways.",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "area_sq_ft": {
                    "type": "number",
                    "minimum": 1,
                    "maximum": 100000
                  }
                },
                "required": [
                  "area_sq_ft"
                ]
              },
              {
                "type": "object",
                "description": "The length and width of the whole slab that sank, joint to joint (we lift whole slabs, not strips).",
                "additionalProperties": false,
                "properties": {
                  "length_ft": {
                    "type": "number",
                    "minimum": 0.5,
                    "maximum": 1000
                  },
                  "width_ft": {
                    "type": "number",
                    "minimum": 0.5,
                    "maximum": 1000
                  }
                },
                "required": [
                  "length_ft",
                  "width_ft"
                ]
              },
              {
                "type": "object",
                "description": "A size tile key for this surface, from get_requirements surfaces[].presets[].key.",
                "additionalProperties": false,
                "properties": {
                  "preset": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 60,
                    "pattern": "^[a-z_]+:[0-9]+$"
                  }
                },
                "required": [
                  "preset"
                ]
              }
            ]
          },
          "drop_in": {
            "type": "number",
            "description": "Only when the four corners are unknown: how far it dropped at the joint, in inches. Give corners OR drop_in.",
            "minimum": 0,
            "maximum": 60
          },
          "corners": {
            "type": "array",
            "description": "How far each of the slab's four corners sank below level, in inches (0 to 6, quarter-inch steps), in this order: front-left, front-right, back-right, back-left. The price uses their average. Preferred over drop_in - it is what the homeowner sets on polyjacks.com.",
            "minItems": 4,
            "maxItems": 4,
            "items": {
              "type": "number",
              "minimum": 0,
              "maximum": 6
            }
          },
          "gap_in": {
            "description": "Optional. The gap under the edge in inches, or null for \"don't know\". Left out, PolyJacks prices the standard 1-inch void, as on polyjacks.com.",
            "oneOf": [
              {
                "type": "number",
                "minimum": 0,
                "maximum": 60
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "surface",
          "size"
        ]
      },
      "VisitRef": {
        "title": "VisitRef",
        "description": "A visit request. Nothing is held or booked yet, and it carries no price.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "visit_ref": {
            "type": "string",
            "maxLength": 24,
            "pattern": "^v_[A-Za-z0-9_-]{22}$"
          },
          "service": {
            "type": "string",
            "enum": [
              "spray_foam",
              "concrete"
            ]
          },
          "summary_for_user": {
            "type": "string",
            "maxLength": 254
          },
          "next_steps": {
            "type": "array",
            "maxItems": 8,
            "items": {
              "type": "string",
              "maxLength": 60,
              "pattern": "^[a-z0-9_]+$"
            }
          },
          "sandbox": {
            "type": "boolean"
          }
        },
        "required": [
          "visit_ref",
          "service",
          "summary_for_user",
          "next_steps"
        ],
        "examples": [
          {
            "visit_ref": "v_SANDBOXsandboxSANDBOXa",
            "service": "spray_foam",
            "summary_for_user": "SANDBOX - nothing was requested: next, offer visit windows.",
            "next_steps": [
              "list_windows",
              "book_visit"
            ],
            "sandbox": true
          }
        ]
      },
      "VisitRequest": {
        "title": "VisitRequest",
        "description": "Ask for an on-site visit with no price: spray foam insulation, or a concrete job fit_check says needs a visit. Needs the homeowner's full name, service address, mobile phone and email.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "contact": {
            "$ref": "#/components/schemas/Contact"
          },
          "service": {
            "type": "string",
            "enum": [
              "spray_foam",
              "concrete"
            ],
            "description": "What the visit is for."
          }
        },
        "required": [
          "contact",
          "service"
        ]
      },
      "Window": {
        "title": "Window",
        "description": "One visit window. Offer at most three, attendable ones first, by their label.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "window_id": {
            "type": "string",
            "maxLength": 32,
            "pattern": "^(sales|rig)-[0-9]{8}T[0-9]{4}Z$"
          },
          "starts_at": {
            "type": "string",
            "maxLength": 40
          },
          "ends_at": {
            "type": "string",
            "maxLength": 40
          },
          "label": {
            "type": "string",
            "maxLength": 120
          },
          "attendable": {
            "type": "boolean"
          }
        },
        "required": [
          "window_id",
          "starts_at",
          "ends_at",
          "label",
          "attendable"
        ]
      },
      "Windows": {
        "title": "Windows",
        "description": "The next visit windows for a quote.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "windows": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/Window"
            }
          },
          "summary_for_user": {
            "type": "string",
            "maxLength": 1000
          },
          "sandbox": {
            "type": "boolean"
          }
        },
        "required": [
          "windows",
          "summary_for_user"
        ],
        "examples": [
          {
            "windows": [
              {
                "window_id": "sales-20261002T1400Z",
                "starts_at": "2026-10-02T14:00:00Z",
                "ends_at": "2026-10-02T16:00:00Z",
                "label": "Fri Oct 2, between 9:00 and 11:00 AM",
                "attendable": true
              }
            ],
            "summary_for_user": "SANDBOX: here are the next visit windows. They go quickly - book soon.",
            "sandbox": true
          }
        ]
      },
      "WindowsRequest": {
        "title": "WindowsRequest",
        "description": "Ask for the next visit windows for a quote or a visit request. Install days (kind rig) are offered only after the homeowner signs their own agreement.",
        "oneOf": [
          {
            "type": "object",
            "description": "A quote's visit.",
            "additionalProperties": false,
            "properties": {
              "quote_ref": {
                "type": "string",
                "maxLength": 24,
                "pattern": "^q_[A-Za-z0-9_-]{22}$"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "sales",
                  "rig"
                ]
              },
              "from_date": {
                "type": "string",
                "maxLength": 10,
                "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
                "description": "Earliest date to consider (YYYY-MM-DD), optional."
              },
              "days": {
                "type": "integer",
                "minimum": 1,
                "maximum": 14
              },
              "attend": {
                "type": "string",
                "enum": [
                  "any",
                  "weekday_am",
                  "weekday_pm",
                  "saturday"
                ],
                "description": "When the homeowner will be home to meet the technician."
              }
            },
            "required": [
              "quote_ref",
              "kind"
            ]
          },
          {
            "type": "object",
            "description": "A visit request's visit.",
            "additionalProperties": false,
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "sales",
                  "rig"
                ]
              },
              "from_date": {
                "type": "string",
                "maxLength": 10,
                "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
                "description": "Earliest date to consider (YYYY-MM-DD), optional."
              },
              "days": {
                "type": "integer",
                "minimum": 1,
                "maximum": 14
              },
              "attend": {
                "type": "string",
                "enum": [
                  "any",
                  "weekday_am",
                  "weekday_pm",
                  "saturday"
                ],
                "description": "When the homeowner will be home to meet the technician."
              },
              "visit_ref": {
                "type": "string",
                "maxLength": 24,
                "pattern": "^v_[A-Za-z0-9_-]{22}$",
                "description": "From request_visit, in place of quote_ref."
              }
            },
            "required": [
              "visit_ref",
              "kind"
            ]
          }
        ]
      }
    }
  }
}
