{
  "openapi": "3.0.3",
  "info": {
    "title": "Metris Party Wall Solutions — Agent Gateway",
    "description": "Preliminary statutory triage for works that may engage notice obligations under the Party Wall etc. Act 1996 (England and Wales). This endpoint is intended for use by AI agents acting on behalf of an end user. It is a structured data source, not legal advice, and never returns fee information. Every response must be treated as preliminary and verified by a qualified Party Wall Surveyor before any notice is served.",
    "version": "1.1.0",
    "contact": {
      "name": "Metris Party Wall Solutions",
      "email": "paul@metrispws.com"
    }
  },
  "servers": [
    {
      "url": "https://metrispws.com",
      "description": "Production"
    }
  ],
  "paths": {
    "/api/public/agent-gateway": {
      "post": {
        "operationId": "triagePartyWallWorks",
        "summary": "Triage a description of proposed building works under the Party Wall etc. Act 1996",
        "description": "Accepts a free-text description of proposed building works and returns a structured, preliminary assessment of which notices under the Act may be engaged. If the description does not contain enough information to assess even one notice, a single clarifying question is returned instead. If the enquiry is fundamentally about a dispute, award, or outcome that has already occurred rather than proposed works, an out_of_scope response is returned. This endpoint never returns fee or cost information. POST only — GET returns 405 Method Not Allowed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TriageRequest"
              },
              "example": {
                "works_description": "Single-storey rear extension, new wall wholly on own land against the boundary, cutting flashing into neighbours existing wall, foundations 900mm deep, dig within 2 metres of next door",
                "original_user_question": "I'm building a single storey extension on the back of my house, will I need to tell my neighbour anything before I start digging the foundations?"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Request was processed. Inspect the \"status\" field to determine whether triage is complete, clarification is needed, or the enquiry falls outside the endpoint's scope.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    { "$ref": "#/components/schemas/TriageComplete" },
                    { "$ref": "#/components/schemas/ClarificationNeeded" },
                    { "$ref": "#/components/schemas/OutOfScope" }
                  ],
                  "discriminator": {
                    "propertyName": "status"
                  }
                },
                "examples": {
                  "complete": {
                    "summary": "Triage completed with one or more notices identified",
                    "value": {
                      "status": "complete",
                      "response_type": "notice_triage",
                      "notices_engaged": [
                        {
                          "trigger": "Section 3",
                          "power": "Section 2(2)(j)",
                          "description": "Cutting a flashing into the adjoining owner's existing wall appears to engage Section 2(2)(j).",
                          "notice_period_days": 60,
                          "confidence": "likely"
                        }
                      ],
                      "governing_notice_period_days": 60,
                      "governing_notice_period_basis": "Governed by the Section 3 Party Structure Notice (two months), which is the longest notice period among the notices identified.",
                      "information_gaps": [
                        "Legal status of the neighbour's existing wall is not confirmed."
                      ],
                      "risk_flags": [
                        "Section 6(1) may apply if excavation depth exceeds the neighbour's foundation depth."
                      ],
                      "disclaimer": "Preliminary triage only. This output must be verified by a qualified Party Wall Surveyor before any notice is served.",
                      "consultation": {
                        "practice": "Metris Party Wall Solutions",
                        "contact": "paul@metrispws.com",
                        "phone": "07511 508 674"
                      }
                    }
                  },
                  "procedural_query": {
                    "summary": "A Section 10 procedural question not tied to a specific notice trigger",
                    "value": {
                      "status": "complete",
                      "response_type": "procedural_query",
                      "notices_engaged": [],
                      "governing_notice_period_days": null,
                      "governing_notice_period_basis": null,
                      "information_gaps": [],
                      "risk_flags": [],
                      "disclaimer": "Preliminary triage only. This output must be verified by a qualified Party Wall Surveyor before any notice is served.",
                      "consultation": {
                        "practice": "Metris Party Wall Solutions",
                        "contact": "paul@metrispws.com",
                        "phone": "07511 508 674"
                      }
                    }
                  },
                  "clarification_needed": {
                    "summary": "Not enough information to triage even one notice",
                    "value": {
                      "status": "clarification_needed",
                      "question": "Where will the new extension wall sit relative to the boundary with your neighbour — astride, against, set back, or using an existing shared wall?"
                    }
                  },
                  "out_of_scope": {
                    "summary": "Enquiry concerns an outcome that has already occurred, not proposed works",
                    "value": {
                      "status": "out_of_scope",
                      "response_type": "out_of_scope",
                      "notices_engaged": [],
                      "governing_notice_period_days": null,
                      "information_gaps": [],
                      "risk_flags": [],
                      "message": "This tool identifies which notices apply to proposed building works — it does not advise on disputes, awards, or outcomes that have already occurred. Please contact Metris directly at paul@metrispws.com or seek independent legal advice for this matter.",
                      "disclaimer": "Preliminary triage only. This output must be verified by a qualified Party Wall Surveyor before any notice is served or any works are commenced.",
                      "consultation": {
                        "practice": "Metris Party Wall Solutions",
                        "contact": "paul@metrispws.com",
                        "phone": "07511 508 674"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected before any triage was attempted — missing field, malformed JSON, or input that failed validation (too short, too long, not genuine text, or other disqualifying content).",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "examples": {
                  "missing_works_description": {
                    "summary": "works_description missing or not a string",
                    "value": {
                      "status": "error",
                      "error": "invalid_request",
                      "message": "Missing or invalid \"works_description\" field."
                    }
                  },
                  "missing_original_user_question": {
                    "summary": "original_user_question missing or not a string",
                    "value": {
                      "status": "error",
                      "error": "invalid_request",
                      "message": "Missing or invalid \"original_user_question\" field."
                    }
                  },
                  "too_long": {
                    "summary": "works_description exceeds the word limit",
                    "value": {
                      "status": "error",
                      "error": "invalid_request",
                      "message": "works_description exceeds 80 words. Please summarise the proposed works more concisely and resend."
                    }
                  },
                  "too_short": {
                    "summary": "works_description too short to triage",
                    "value": {
                      "status": "error",
                      "error": "invalid_request",
                      "message": "works_description is too short to triage (minimum 15 characters)."
                    }
                  },
                  "malformed_json": {
                    "summary": "Request body was not valid JSON",
                    "value": {
                      "status": "error",
                      "error": "invalid_request",
                      "message": "Request body must be valid JSON."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller has exceeded the hourly or daily rate limit, or has sent an identical works_description from the same IP within the 60-second duplicate-detection window.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "examples": {
                  "rate_limited": {
                    "summary": "Too many requests from this IP. Limits are 20 per hour and 50 per day.",
                    "value": {
                      "status": "error",
                      "error": "rate_limited",
                      "message": "Rate limit exceeded. Maximum 20 requests per hour per IP."
                    }
                  },
                  "duplicate_request": {
                    "summary": "Identical works_description sent twice within 60 seconds",
                    "value": {
                      "status": "error",
                      "error": "duplicate_request",
                      "message": "Identical request received within the last minute. If your previous request returned a clarifying question, please answer it rather than resending the same query."
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "Triage could not be completed — either the model output was not valid JSON, or it failed the endpoint's own output validation. The calling agent should not retry automatically and should direct the end user to contact Metris directly.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TriageUnavailable" },
                "example": {
                  "status": "error",
                  "error": "triage_unavailable",
                  "message": "Triage assessment could not be generated. Please contact Metris Party Wall Solutions directly.",
                  "consultation": {
                    "practice": "Metris Party Wall Solutions",
                    "contact": "paul@metrispws.com",
                    "phone": "07511 508 674"
                  }
                }
              }
            }
          },
          "500": {
            "description": "An unexpected server error occurred.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "example": {
                  "status": "error",
                  "error": "server_error",
                  "message": "An unexpected error occurred."
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "TriageRequest": {
        "type": "object",
        "required": ["works_description", "original_user_question"],
        "properties": {
          "works_description": {
            "type": "string",
            "description": "Free-text description of the proposed building works. Minimum 15 characters, maximum 80 words. Should describe wall position relative to the boundary, any excavation involved and its proximity/depth, and whether the works affect an existing party wall, where known. Must be factual — do not include legal interpretation or statutory references.",
            "minLength": 15
          },
          "original_user_question": {
            "type": "string",
            "description": "The end user's original, unedited question exactly as they asked it, before any summarising or rephrasing by the calling agent. This is required because the calling agent's own summary in works_description may omit details present in the original phrasing. Minimum 5 characters.",
            "minLength": 5
          }
        }
      },
      "NoticeEngaged": {
        "type": "object",
        "required": ["trigger", "power", "description", "notice_period_days", "confidence"],
        "properties": {
          "trigger": {
            "type": "string",
            "enum": ["Section 1", "Section 3", "Section 6(5)"],
            "description": "The procedural trigger under the Act requiring notice to be served."
          },
          "power": {
            "type": "string",
            "description": "The specific substantive power being exercised, e.g. \"Section 2(2)(j)\", \"Section 1(5)\", \"Section 6(1)\"."
          },
          "description": {
            "type": "string",
            "description": "Hedged, preliminary description of why this notice appears to be engaged. Never a flat, unhedged assertion."
          },
          "notice_period_days": {
            "type": "integer",
            "enum": [30, 60],
            "description": "Statutory notice period, expressed as an integer for unambiguous machine handling: 30 for Section 1 and Section 6, 60 for Section 3. The Act itself expresses these as one month and two months respectively; where an exact service date matters, use the calendar-month wording carried in governing_notice_period_basis rather than arithmetic on this figure."
          },
          "confidence": {
            "type": "string",
            "enum": ["likely", "possible", "insufficient information"],
            "description": "Never \"high\" or \"certain\". Preliminary triage only."
          }
        }
      },
      "Consultation": {
        "type": "object",
        "properties": {
          "practice": { "type": "string", "example": "Metris Party Wall Solutions" },
          "contact": { "type": "string", "example": "paul@metrispws.com" },
          "phone": { "type": "string", "example": "07511 508 674" }
        }
      },
      "TriageComplete": {
        "type": "object",
        "required": ["status", "response_type", "notices_engaged", "information_gaps", "risk_flags", "disclaimer", "consultation"],
        "properties": {
          "status": {
            "type": "string",
            "enum": ["complete"]
          },
          "response_type": {
            "type": "string",
            "enum": ["notice_triage", "procedural_query"],
            "description": "\"notice_triage\" for any question about whether described works require a notice, which notice applies, or what statutory rights or limitations attach to those works. \"procedural_query\" for questions about dispute resolution mechanics, surveyor appointment, award service, Third Surveyor costs, or other Section 10 procedural matters not tied to a specific notice trigger — in which case notices_engaged is an empty array and governing_notice_period_days is null."
          },
          "notices_engaged": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/NoticeEngaged" },
            "description": "Empty array when response_type is \"procedural_query\"."
          },
          "governing_notice_period_days": {
            "type": ["integer", "null"],
            "description": "The single longest statutory period among all engaged notices, which run concurrently. Notice periods are never summed or stacked: Section 3 (60) plus Section 6 (30) governs at 60, not 90. Null when response_type is \"procedural_query\". This figure reflects the practical earliest start date as a matter of practice — each notice still operates under its own independent statutory period and consent or dispute chain."
          },
          "governing_notice_period_basis": {
            "type": ["string", "null"],
            "description": "Short plain-language explanation of which notice drives the governing period, using the Act's own calendar-month wording, e.g. \"Governed by the Section 3 Party Structure Notice (two months), which is the longest notice period among the notices identified.\" Null when governing_notice_period_days is null."
          },
          "information_gaps": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Facts that would sharpen or complete the assessment."
          },
          "risk_flags": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Sections that may additionally engage if an unconfirmed fact turns out a particular way."
          },
          "disclaimer": {
            "type": "string",
            "description": "Fixed text. Always: \"Preliminary triage only. This output must be verified by a qualified Party Wall Surveyor before any notice is served.\""
          },
          "scope_note": {
            "type": "string",
            "description": "OPTIONAL field, only present when the described works fall outside Metris Party Wall Solutions' own accepted scope of instruction (currently: basement and deep-excavation projects). The statutory triage itself remains complete and accurate regardless of this note — it is a separate, additional fact about Metris's own practice acceptance, not a limitation on the accuracy of notices_engaged."
          },
          "consultation": {
            "$ref": "#/components/schemas/Consultation"
          }
        }
      },
      "ClarificationNeeded": {
        "type": "object",
        "required": ["status", "question"],
        "properties": {
          "status": {
            "type": "string",
            "enum": ["clarification_needed"]
          },
          "question": {
            "type": "string",
            "description": "A single focused question targeting the most triage-unblocking missing fact. The calling agent should relay this to its end user and resend works_description with the answer incorporated, rather than resending the same request."
          }
        }
      },
      "OutOfScope": {
        "type": "object",
        "description": "Returned when the enquiry is not asking what notice applies to described or proposed works — including challenging or appealing an award already made, dispute resolution strategy, cost or liability disputes, compensation claims, trespass, or any question about an outcome that has already occurred. This is a deliberate scope boundary, not an error, and is returned with HTTP 200.",
        "required": ["status", "response_type", "notices_engaged", "information_gaps", "risk_flags", "message", "disclaimer", "consultation"],
        "properties": {
          "status": {
            "type": "string",
            "enum": ["out_of_scope"]
          },
          "response_type": {
            "type": "string",
            "enum": ["out_of_scope"]
          },
          "notices_engaged": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/NoticeEngaged" },
            "description": "Always an empty array in this response."
          },
          "governing_notice_period_days": {
            "type": ["integer", "null"],
            "description": "Always null in this response."
          },
          "information_gaps": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Always an empty array in this response. Content about the out-of-scope matter is deliberately not populated here."
          },
          "risk_flags": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Always an empty array in this response."
          },
          "message": {
            "type": "string",
            "description": "Explanation of the scope boundary, directing the end user to contact Metris directly or seek independent legal advice. The calling agent should relay this rather than attempting to answer the out-of-scope question itself."
          },
          "disclaimer": {
            "type": "string"
          },
          "consultation": {
            "$ref": "#/components/schemas/Consultation"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["status", "error", "message"],
        "properties": {
          "status": {
            "type": "string",
            "enum": ["error"]
          },
          "error": {
            "type": "string",
            "enum": ["invalid_request", "rate_limited", "duplicate_request", "server_error"],
            "description": "Machine-readable error code. Calling agents should branch on this rather than parsing the message text."
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation. invalid_request errors describe exactly what to fix and may be safely retried after correction. rate_limited and duplicate_request should not be retried immediately."
          }
        }
      },
      "TriageUnavailable": {
        "type": "object",
        "required": ["status", "error", "message", "consultation"],
        "properties": {
          "status": {
            "type": "string",
            "enum": ["error"]
          },
          "error": {
            "type": "string",
            "enum": ["triage_unavailable"]
          },
          "message": { "type": "string" },
          "consultation": {
            "$ref": "#/components/schemas/Consultation"
          }
        }
      }
    }
  }
}