{
  "openapi": "3.1.0",
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "info": {
    "title": "MyCatchment home assessment API",
    "version": "2.0.0",
    "description": "The production assessment API is available for configured catchment coverage in Vancouver School District (SD39) and North Vancouver School District (SD44). Address outcomes may be unresolved, ambiguous, unsupported or unavailable. Requested school years remain unverified. The V1 response names applicable catchment schools and carries each existing individual MyCatchment rating."
  },
  "paths": {
    "/api/v1/assessments": {
      "post": {
        "operationId": "createAssessment",
        "summary": "Assess one to three candidate homes",
        "x-availability": "available",
        "description": "The production API is available for configured catchment coverage in Vancouver School District (SD39) and North Vancouver School District (SD44). Address outcomes may be unresolved, ambiguous, unsupported or unavailable; these cases may return no matched school. Requested school years remain unverified. Request text is transient. Maximum 3072 UTF-8 body bytes, three addresses, 160 UTF-16 code units per address, two concurrent geocoder lookups, 2500 ms upstream timeout and 8000 ms total timeout. Every response uses Cache-Control: no-store. No account, coordinates, family profile or arbitrary URL input. A successful schema 2.0 response contains every configured stage when district applicability is known, each matched school name and exact individual rating, plus the required district-verification notice. Unknown applicability has no stage claim. A score of zero is distinct from a missing rating. Total serialized output is limited to 16384 UTF-8 bytes and is never truncated. Sanitized non-200 errors make no school assertion.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Request"
              },
              "examples": {
                "oneHome": {
                  "value": {
                    "addresses": [
                      "100 Example St, Vancouver, BC"
                    ],
                    "schoolYear": "2026/27"
                  }
                },
                "threeHomes": {
                  "value": {
                    "addresses": [
                      "100 Example St, Vancouver, BC",
                      "200 Sample Ave, Vancouver, BC",
                      "300 Demo Rd, North Vancouver, BC"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validated schema 2.0 batch in input order, including candidate-level failures. Each matched school has its individual score or explicit absence. Every 200 carries the exact required district-verification notice.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Result"
                },
                "examples": {
                  "resolved": {
                    "summary": "Synthetic location checked against committed school records",
                    "value": {
                      "schemaVersion": "2.0",
                      "datasetVersion": "assessment-4b94d4dba8a9d5d1c72c201fbe3adf4a30d7ac9fc63c906fc8521e74fc9cc9ad",
                      "requestedSchoolYear": "2026/27",
                      "notices": [
                        {
                          "code": "district_verification_required",
                          "version": "1",
                          "displayRequired": true,
                          "text": "Confirm the catchment school and enrolment requirements for your address and intended school year with the school district before making a housing or enrolment decision. MyCatchment's results are informational and do not guarantee admission, a place at a particular school, or program eligibility.",
                          "reference": "/about#what-this-doesnt-replace"
                        }
                      ],
                      "results": [
                        {
                          "index": 0,
                          "resolution": "resolved",
                          "coverage": "supported",
                          "stages": [
                            {
                              "stageId": "elementary",
                              "label": "Elementary",
                              "match": "single",
                              "schools": [
                                {
                                  "name": "David Livingstone Elementary",
                                  "url": "/schools/david-livingstone-elementary",
                                  "rating": {
                                    "value": 9.6,
                                    "scale": 10,
                                    "methodologyRef": "/about#methodology",
                                    "absenceReason": null
                                  }
                                }
                              ]
                            },
                            {
                              "stageId": "secondary",
                              "label": "Secondary",
                              "match": "single",
                              "schools": [
                                {
                                  "name": "Sir Charles Tupper Secondary",
                                  "url": "/schools/sir-charles-tupper-secondary",
                                  "rating": {
                                    "value": 4.9,
                                    "scale": 10,
                                    "methodologyRef": "/about#methodology",
                                    "absenceReason": null
                                  }
                                }
                              ]
                            }
                          ],
                          "schoolYearStatus": "unverified",
                          "limitations": [
                            "year_not_verified"
                          ]
                        }
                      ]
                    },
                    "description": "Independent response illustration from a controlled synthetic location inside committed Vancouver polygons. It is unrelated to the fictional request examples and asserts no postal-address assignment. School names, canonical pages, scores and dataset hash are from the committed snapshot."
                  },
                  "unsupported": {
                    "summary": "Synthetic unsupported location with no school claim",
                    "value": {
                      "schemaVersion": "2.0",
                      "datasetVersion": "assessment-4b94d4dba8a9d5d1c72c201fbe3adf4a30d7ac9fc63c906fc8521e74fc9cc9ad",
                      "requestedSchoolYear": "2026/27",
                      "notices": [
                        {
                          "code": "district_verification_required",
                          "version": "1",
                          "displayRequired": true,
                          "text": "Confirm the catchment school and enrolment requirements for your address and intended school year with the school district before making a housing or enrolment decision. MyCatchment's results are informational and do not guarantee admission, a place at a particular school, or program eligibility.",
                          "reference": "/about#what-this-doesnt-replace"
                        }
                      ],
                      "results": [
                        {
                          "index": 0,
                          "resolution": "resolved",
                          "coverage": "unsupported",
                          "stages": [],
                          "schoolYearStatus": "unverified",
                          "limitations": [
                            "outside_supported_area",
                            "year_not_verified"
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMedia"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    }
  },
  "components": {
    "responses": {
      "BadRequest": {
        "description": "Malformed JSON or invalid fields.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "code": "invalid_request",
              "message": "Invalid assessment request."
            }
          }
        },
        "headers": {
          "Cache-Control": {
            "schema": {
              "const": "no-store"
            }
          }
        }
      },
      "TooLarge": {
        "description": "Body exceeds 3072 UTF-8 bytes.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "code": "payload_too_large",
              "message": "Assessment request is too large."
            }
          }
        },
        "headers": {
          "Cache-Control": {
            "schema": {
              "const": "no-store"
            }
          }
        }
      },
      "UnsupportedMedia": {
        "description": "Content-Type must be application/json with no unsupported charset.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "code": "unsupported_media_type",
              "message": "Use application/json."
            }
          }
        },
        "headers": {
          "Cache-Control": {
            "schema": {
              "const": "no-store"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Throttled. Retry-After carries seconds.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 3600
            }
          },
          "Cache-Control": {
            "schema": {
              "const": "no-store"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "code": "rate_limited",
              "message": "Please retry later.",
              "retryAfterSeconds": 60
            }
          }
        }
      },
      "Unavailable": {
        "description": "The whole batch is withheld after unavailable dependencies, deadline, confirmed sampled mismatch, invalid result or output cap. No candidate result is returned; processing may already have occurred.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "code": "service_unavailable",
              "message": "Assessment is temporarily unavailable."
            }
          }
        },
        "headers": {
          "Cache-Control": {
            "schema": {
              "const": "no-store"
            }
          }
        }
      }
    },
    "schemas": {
      "SchoolYear": {
        "type": "string",
        "pattern": "^20[0-9]{2}/[0-9]{2}$",
        "description": "YYYY/YY with consecutive years; runtime validator enforces this and start year <= 2098.",
        "enum": [
          "2000/01",
          "2001/02",
          "2002/03",
          "2003/04",
          "2004/05",
          "2005/06",
          "2006/07",
          "2007/08",
          "2008/09",
          "2009/10",
          "2010/11",
          "2011/12",
          "2012/13",
          "2013/14",
          "2014/15",
          "2015/16",
          "2016/17",
          "2017/18",
          "2018/19",
          "2019/20",
          "2020/21",
          "2021/22",
          "2022/23",
          "2023/24",
          "2024/25",
          "2025/26",
          "2026/27",
          "2027/28",
          "2028/29",
          "2029/30",
          "2030/31",
          "2031/32",
          "2032/33",
          "2033/34",
          "2034/35",
          "2035/36",
          "2036/37",
          "2037/38",
          "2038/39",
          "2039/40",
          "2040/41",
          "2041/42",
          "2042/43",
          "2043/44",
          "2044/45",
          "2045/46",
          "2046/47",
          "2047/48",
          "2048/49",
          "2049/50",
          "2050/51",
          "2051/52",
          "2052/53",
          "2053/54",
          "2054/55",
          "2055/56",
          "2056/57",
          "2057/58",
          "2058/59",
          "2059/60",
          "2060/61",
          "2061/62",
          "2062/63",
          "2063/64",
          "2064/65",
          "2065/66",
          "2066/67",
          "2067/68",
          "2068/69",
          "2069/70",
          "2070/71",
          "2071/72",
          "2072/73",
          "2073/74",
          "2074/75",
          "2075/76",
          "2076/77",
          "2077/78",
          "2078/79",
          "2079/80",
          "2080/81",
          "2081/82",
          "2082/83",
          "2083/84",
          "2084/85",
          "2085/86",
          "2086/87",
          "2087/88",
          "2088/89",
          "2089/90",
          "2090/91",
          "2091/92",
          "2092/93",
          "2093/94",
          "2094/95",
          "2095/96",
          "2096/97",
          "2097/98",
          "2098/99"
        ]
      },
      "Request": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "addresses"
        ],
        "properties": {
          "addresses": {
            "type": "array",
            "minItems": 1,
            "maxItems": 3,
            "items": {
              "type": "string",
              "minLength": 3,
              "maxLength": 160,
              "description": "Text civic address, no control characters or URL. Runtime length is at most 160 UTF-16 code units; JSON Schema maxLength counts Unicode code points. Surrounding whitespace is trimmed before processing and never echoed.",
              "pattern": "^(?![ \\t]*(?:[A-Za-z][A-Za-z0-9+.-]*://|www\\.))[^\\u0000-\\u001f\\u007f]+$"
            }
          },
          "schoolYear": {
            "$ref": "#/components/schemas/SchoolYear"
          }
        }
      },
      "Match": {
        "type": "string",
        "enum": [
          "single",
          "none",
          "ambiguous",
          "unavailable"
        ]
      },
      "Candidate": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "index",
          "resolution",
          "coverage",
          "stages",
          "schoolYearStatus",
          "limitations"
        ],
        "properties": {
          "index": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2
          },
          "resolution": {
            "type": "string",
            "enum": [
              "resolved",
              "needs_clarification",
              "not_found",
              "unavailable"
            ]
          },
          "coverage": {
            "type": "string",
            "enum": [
              "supported",
              "partial",
              "unsupported",
              "unknown"
            ]
          },
          "stages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Stage"
            },
            "description": "Exactly the server-configured stage IDs, labels and order for this candidate when district applicability is known. An empty list with unknown coverage means applicability was not established; an empty list with unsupported coverage means the area is outside configured service."
          },
          "schoolYearStatus": {
            "const": "unverified",
            "description": "Current and future school years are not verified by this response."
          },
          "limitations": {
            "type": "array",
            "uniqueItems": true,
            "maxItems": 11,
            "items": {
              "type": "string",
              "enum": [
                "address_needs_clarification",
                "address_not_found",
                "address_service_unavailable",
                "outside_supported_area",
                "coverage_partial",
                "geometry_ambiguous",
                "catchment_missing",
                "geometry_unavailable",
                "year_not_verified",
                "evidence_missing",
                "evidence_unavailable"
              ]
            }
          }
        },
        "allOf": [
          {
            "if": {
              "properties": {
                "resolution": {
                  "not": {
                    "const": "resolved"
                  }
                }
              }
            },
            "then": {
              "properties": {
                "coverage": {
                  "const": "unknown"
                },
                "stages": {
                  "maxItems": 0
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "coverage": {
                  "enum": [
                    "unknown",
                    "unsupported"
                  ]
                }
              }
            },
            "then": {
              "properties": {
                "stages": {
                  "maxItems": 0
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "coverage": {
                  "const": "unsupported"
                }
              }
            },
            "then": {
              "properties": {
                "limitations": {
                  "contains": {
                    "const": "outside_supported_area"
                  }
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "coverage": {
                  "const": "partial"
                }
              }
            },
            "then": {
              "properties": {
                "limitations": {
                  "contains": {
                    "const": "coverage_partial"
                  }
                }
              }
            }
          }
        ],
        "description": "Runtime validation additionally requires exact input index, school-year echo and uncertainty, and stage completeness against the trusted per-address district registry."
      },
      "Result": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "datasetVersion",
          "requestedSchoolYear",
          "notices",
          "results"
        ],
        "properties": {
          "schemaVersion": {
            "const": "2.0"
          },
          "datasetVersion": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "pattern": "^[A-Za-z0-9._-]+$"
          },
          "requestedSchoolYear": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SchoolYear"
              },
              {
                "type": "null"
              }
            ]
          },
          "notices": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1,
            "items": {
              "$ref": "#/components/schemas/Notice"
            },
            "description": "Exactly one required notice in every successful response, including batches with only unsupported or unavailable candidates. Notice envelope is bounded to 1024 UTF-8 bytes."
          },
          "results": {
            "type": "array",
            "minItems": 1,
            "maxItems": 3,
            "items": {
              "$ref": "#/components/schemas/Candidate"
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "invalid_request",
              "payload_too_large",
              "unsupported_media_type",
              "rate_limited",
              "service_unavailable"
            ]
          },
          "message": {
            "type": "string",
            "maxLength": 160
          },
          "retryAfterSeconds": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3600
          }
        }
      },
      "Notice": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "code",
          "version",
          "displayRequired",
          "text",
          "reference"
        ],
        "properties": {
          "code": {
            "const": "district_verification_required"
          },
          "version": {
            "const": "1"
          },
          "displayRequired": {
            "const": true
          },
          "text": {
            "const": "Confirm the catchment school and enrolment requirements for your address and intended school year with the school district before making a housing or enrolment decision. MyCatchment's results are informational and do not guarantee admission, a place at a particular school, or program eligibility."
          },
          "reference": {
            "const": "/about#what-this-doesnt-replace"
          }
        },
        "description": "Static, versioned district-verification text. Clients must preserve this notice when presenting school implications; displayRequired expresses that expectation, not proof of third-party display."
      },
      "Stage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "stageId",
          "label",
          "match",
          "schools"
        ],
        "properties": {
          "stageId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40,
            "pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$"
          },
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "match": {
            "$ref": "#/components/schemas/Match"
          },
          "schools": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RatedSchool"
            }
          }
        },
        "allOf": [
          {
            "if": {
              "properties": {
                "match": {
                  "const": "single"
                }
              }
            },
            "then": {
              "properties": {
                "schools": {
                  "minItems": 1,
                  "maxItems": 1
                }
              }
            },
            "else": {
              "properties": {
                "schools": {
                  "maxItems": 0
                }
              }
            }
          }
        ],
        "description": "A single authoritative match contains one canonical school. None, ambiguous and unavailable contain no guessed school."
      },
      "RatedSchool": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "url",
          "rating"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Canonical public school-directory name, never address text."
          },
          "url": {
            "type": "string",
            "pattern": "^/schools/[a-z0-9]+(-[a-z0-9]+)*$"
          },
          "rating": {
            "$ref": "#/components/schemas/Rating"
          }
        }
      },
      "Rating": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "value",
          "scale",
          "methodologyRef",
          "absenceReason"
        ],
        "properties": {
          "value": {
            "oneOf": [
              {
                "type": "number",
                "minimum": 0,
                "maximum": 10
              },
              {
                "type": "null"
              }
            ]
          },
          "scale": {
            "const": 10
          },
          "methodologyRef": {
            "const": "/about#methodology"
          },
          "absenceReason": {
            "oneOf": [
              {
                "type": "string",
                "enum": [
                  "not_available",
                  "insufficient_evidence"
                ]
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "allOf": [
          {
            "if": {
              "properties": {
                "value": {
                  "type": "null"
                }
              }
            },
            "then": {
              "properties": {
                "absenceReason": {
                  "type": "string",
                  "enum": [
                    "not_available",
                    "insufficient_evidence"
                  ]
                }
              }
            },
            "else": {
              "properties": {
                "absenceReason": {
                  "type": "null"
                }
              }
            }
          }
        ],
        "description": "Exact existing individual school_score_10 for this canonical school and dataset version. Null retains the school with a truthful reason; numeric zero is a real score. No shared assessment period is implied."
      }
    }
  }
}
