{
  "openapi": "3.1.0",
  "info": {
    "title": "Kingsland Fabrications Agent API",
    "version": "2.1.0",
    "description": "Self-contained, consent-gated agent enquiry API for Kingsland Fabrications, a trade-only, supply-only aluminium fabricator. /SKILL.md is optional. The human website form endpoints are intentionally excluded because they do not enforce prepare → review → explicit approval → submit.",
    "contact": {
      "name": "Kingsland Fabrications",
      "url": "https://www.kingslandfabrications.co.uk/contact/",
      "email": "sales@kingslandfabrications.co.uk"
    }
  },
  "servers": [
    { "url": "https://www.kingslandfabrications.co.uk" }
  ],
  "externalDocs": {
    "description": "Verified facts and self-contained enquiry question sets; the enhanced skill linked there is optional",
    "url": "https://www.kingslandfabrications.co.uk/agent-facts.json"
  },
  "x-agent-guidance": {
    "skill_required": false,
    "business_fit": "Proceed only for a trade buyer seeking made-to-order, supply-only aluminium fabrication. Kingsland does not install or sell direct to homeowners.",
    "first_response_order": [
      "Already known",
      "Kingsland fit",
      "What Kingsland still needs"
    ],
    "question_policy": "Reuse all legitimate known details. For a quote, ask one numbered batch of 4–8 or fewer unanswered, service-relevant questions. Allow unknown for optional details. Never ask for a budget or generic installation details.",
    "common_requirements": [
      "Contact name, email, phone and trade company where applicable",
      "Product and new or replacement scope",
      "Leaf, doorset, screen, frontage, panel or elevation configuration",
      "Quantity and dimensions, or survey/drawing/photo availability",
      "Relevant glazing, performance, RAL finish, hardware, threshold/access or operator preparation",
      "Delivery postcode and required date/programme"
    ],
    "product_requirements": {
      "commercial_doors": "Leaf-only or doorset; dimensions/survey; manual or automatic-swing preparation; locks, closer, panic/access control; glazing; threshold; RAL; postcode/date.",
      "shopfronts": "Opening/elevation; door and display/signage configuration; glazing; RAL; hardware/access; postcode/date.",
      "windows": "Schedule/dimensions; opening type; glazing/U-value/acoustic/security; RAL; postcode/date.",
      "curtain_walling": "Elevation/grid/dimensions; glazing/infill; integrated doors/vents; finish; postcode/programme.",
      "automatic_sliding_frames": "Opening; single-slide/bi-parting; operator interface; breakout; glazing; finish; postcode/date.",
      "louvres": "Dimensions; free area/airflow; weather/flyscreen; door/panel configuration; finish; postcode/date.",
      "reactive_replacement": "Existing product/profile if known; dimensions/survey/photos; leaf/full scope; make-safe status; SLA/engineer return visit; postcode."
    },
    "workflow": "Prepare without external action; show the exact payload, destinations, hash and expiry; wait for the exact approval statement; approve the unchanged payload; submit once; report only receipt-confirmed destination outcomes.",
    "exact_approval_statement": "I approve this exact enquiry for submission to Kingsland Fabrications",
    "forbidden_agent_actions": [
      "/api/leads",
      "/api/contact",
      "/api/newsletter"
    ]
  },
  "paths": {
    "/api/agent/enquiries/prepare/": {
      "post": {
        "operationId": "prepareAgentEnquiry",
        "summary": "Prepare an exact enquiry for human review",
        "description": "First reuse known facts and gather only the unanswered questions in x-agent-guidance. Then validate the complete payload and store only its SHA-256 hash with a ten-minute expiry. This operation does not create a lead, call the CRM/MES webhook or send email. Return the exact payload and destinations to the user before approval.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/EnquiryPayload" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Prepared for review; exact payload, destinations, hash and expiry returned",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PreparedEnquiry" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "403": { "$ref": "#/components/responses/ForbiddenOrigin" },
          "413": { "$ref": "#/components/responses/BodyTooLarge" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/agent/enquiries/approve/": {
      "post": {
        "operationId": "approveAgentEnquiry",
        "summary": "Bind explicit approval to the unchanged payload",
        "description": "Requires the exact approval statement after human review. Returns a five-minute, single-use token and still performs no lead/email/CRM action.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "preparation_id",
                  "payload_sha256",
                  "payload",
                  "approval_statement"
                ],
                "properties": {
                  "preparation_id": { "type": "string", "format": "uuid" },
                  "payload_sha256": { "$ref": "#/components/schemas/Sha256" },
                  "payload": { "$ref": "#/components/schemas/EnquiryPayload" },
                  "approval_statement": {
                    "type": "string",
                    "const": "I approve this exact enquiry for submission to Kingsland Fabrications"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approval bound; single-use token returned",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApprovedEnquiry" }
              }
            }
          },
          "403": { "$ref": "#/components/responses/ForbiddenOrigin" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/ConsentFailure" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/agent/enquiries/submit/": {
      "post": {
        "operationId": "submitApprovedAgentEnquiry",
        "summary": "Submit one approved, unchanged enquiry",
        "description": "Atomically consumes the unexpired approval token before entering the Kingsland lead pipeline. The receipt reports each destination independently; no email or webhook success is inferred.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "preparation_id",
                  "payload_sha256",
                  "approval_token",
                  "payload"
                ],
                "properties": {
                  "preparation_id": { "type": "string", "format": "uuid" },
                  "payload_sha256": { "$ref": "#/components/schemas/Sha256" },
                  "approval_token": {
                    "type": "string",
                    "pattern": "^[a-f0-9]{64}$"
                  },
                  "payload": { "$ref": "#/components/schemas/EnquiryPayload" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Submission attempted once; destination receipt returned",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SubmissionReceipt" }
              }
            }
          },
          "403": { "$ref": "#/components/responses/ForbiddenOrigin" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/ConsentFailure" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": {
            "description": "The one-time submission was attempted but no complete successful pipeline result was confirmed"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Sha256": {
        "type": "string",
        "pattern": "^[a-f0-9]{64}$"
      },
      "EnquiryPayload": {
        "type": "object",
        "description": "A complete Kingsland trade enquiry. Missing optional details may be acknowledged as unknown in agent_context; do not invent values.",
        "additionalProperties": false,
        "required": ["intent", "contact", "job", "agent_context"],
        "properties": {
          "intent": {
            "type": "string",
            "description": "The real reason for contacting Kingsland. Quote and replacement intents require a product or service.",
            "enum": [
              "trade_quote",
              "reactive_replacement",
              "technical_question",
              "new_trade_account",
              "existing_order",
              "general_trade_enquiry"
            ]
          },
          "contact": {
            "type": "object",
            "description": "The trade contact whose details will be shown in full before approval.",
            "additionalProperties": false,
            "required": ["name", "email", "phone"],
            "properties": {
              "name": { "type": "string", "minLength": 2, "maxLength": 100 },
              "email": { "type": "string", "format": "email", "maxLength": 255 },
              "phone": { "type": "string", "minLength": 10, "maxLength": 20 },
              "company": { "type": "string", "maxLength": 200 }
            }
          },
          "job": {
            "type": "object",
            "description": "The supply-only fabrication brief gathered using the relevant product requirements in x-agent-guidance.",
            "additionalProperties": false,
            "required": ["summary"],
            "properties": {
              "service": { "type": "string", "maxLength": 500 },
              "product": { "type": "string", "maxLength": 500 },
              "summary": { "type": "string", "minLength": 10, "maxLength": 1000 },
              "quantity": { "type": "string", "maxLength": 500 },
              "dimensions": { "type": "string", "maxLength": 500 },
              "survey_or_drawing": { "type": "string", "maxLength": 500 },
              "finish": { "type": "string", "maxLength": 500 },
              "glazing": { "type": "string", "maxLength": 500 },
              "hardware": { "type": "string", "maxLength": 500 },
              "threshold_or_access": { "type": "string", "maxLength": 500 },
              "site_or_delivery_postcode": { "type": "string", "maxLength": 20 },
              "required_by": { "type": "string", "maxLength": 100 },
              "make_safe": {
                "type": "string",
                "enum": ["yes", "no", "unknown", "not_applicable"]
              },
              "attachment_urls": {
                "type": "array",
                "maxItems": 10,
                "description": "Public HTTPS reference URLs. The agent endpoint records but never fetches them.",
                "items": { "type": "string", "format": "uri", "maxLength": 1000 }
              },
              "known_constraints": {
                "type": "array",
                "maxItems": 20,
                "items": { "type": "string", "maxLength": 300 }
              }
            }
          },
          "agent_context": {
            "type": "object",
            "description": "Audit trail showing which known facts were reused and which optional gaps were explicitly left unknown.",
            "additionalProperties": false,
            "required": [
              "known_facts_used",
              "missing_information_acknowledged"
            ],
            "properties": {
              "known_facts_used": {
                "type": "array",
                "maxItems": 30,
                "items": { "type": "string", "maxLength": 300 }
              },
              "missing_information_acknowledged": {
                "type": "array",
                "maxItems": 30,
                "items": { "type": "string", "maxLength": 300 }
              }
            }
          }
        }
      },
      "PreparedEnquiry": {
        "type": "object",
        "required": [
          "ok",
          "stage",
          "preparation_id",
          "payload_sha256",
          "payload",
          "destination",
          "prepare_expires_at"
        ],
        "properties": {
          "ok": { "const": true },
          "stage": { "const": "review" },
          "preparation_id": { "type": "string", "format": "uuid" },
          "payload_sha256": { "$ref": "#/components/schemas/Sha256" },
          "payload": { "$ref": "#/components/schemas/EnquiryPayload" },
          "destination": { "type": "object" },
          "prepared_at": { "type": "string", "format": "date-time" },
          "prepare_expires_at": { "type": "string", "format": "date-time" },
          "next": { "type": "string" }
        }
      },
      "ApprovedEnquiry": {
        "type": "object",
        "required": [
          "ok",
          "stage",
          "preparation_id",
          "payload_sha256",
          "approval_token",
          "approval_expires_at",
          "single_use"
        ],
        "properties": {
          "ok": { "const": true },
          "stage": { "const": "approved" },
          "preparation_id": { "type": "string", "format": "uuid" },
          "payload_sha256": { "$ref": "#/components/schemas/Sha256" },
          "approval_token": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
          "approval_expires_at": { "type": "string", "format": "date-time" },
          "single_use": { "const": true }
        }
      },
      "DeliveryStatus": {
        "type": "string",
        "enum": ["confirmed", "failed", "not_configured", "not_attempted"]
      },
      "SubmissionReceipt": {
        "type": "object",
        "required": [
          "ok",
          "stage",
          "preparation_id",
          "payload_sha256",
          "single_use_token_consumed",
          "receipt"
        ],
        "properties": {
          "ok": { "type": "boolean" },
          "stage": { "enum": ["submitted", "failed"] },
          "preparation_id": { "type": "string", "format": "uuid" },
          "payload_sha256": { "$ref": "#/components/schemas/Sha256" },
          "single_use_token_consumed": { "const": true },
          "receipt": {
            "type": "object",
            "required": [
              "contact_record",
              "lead_record",
              "staff_notification_email",
              "customer_confirmation_email",
              "mes_webhook"
            ],
            "properties": {
              "contact_record": { "$ref": "#/components/schemas/DeliveryStatus" },
              "lead_record": { "$ref": "#/components/schemas/DeliveryStatus" },
              "staff_notification_email": { "$ref": "#/components/schemas/DeliveryStatus" },
              "customer_confirmation_email": { "$ref": "#/components/schemas/DeliveryStatus" },
              "mes_webhook": { "$ref": "#/components/schemas/DeliveryStatus" }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "code": { "type": "string" },
          "message": { "type": "string" }
        }
      }
    },
    "responses": {
      "InvalidRequest": {
        "description": "Invalid payload or JSON",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "ForbiddenOrigin": {
        "description": "Browser Origin is not allowed"
      },
      "BodyTooLarge": {
        "description": "Request exceeds 50,000 bytes"
      },
      "RateLimited": {
        "description": "Too many requests from this network address"
      },
      "NotFound": {
        "description": "Preparation id was not found"
      },
      "ConsentFailure": {
        "description": "Payload altered, approval missing/expired/reused, or consent state conflict",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      }
    }
  },
  "x-excluded-human-form-endpoints": [
    "/api/leads",
    "/api/contact",
    "/api/newsletter"
  ]
}
