{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:cede:schema:geocode:v0",
  "title": "Craton geocoding lookup, v0",
  "description": "The body of GET /geocode: candidate coordinates for an address or place name, for a person to choose between. The lookup is explicit and nothing is written anywhere: Ingest never geocodes on its own, and a coordinate becomes part of a risk object only when the caller writes it there. Every candidate says how precise it is and which public geocoder produced it, so a street center is never mistaken for a rooftop. Zero matches is a 200 with an empty results list.",
  "type": "object",
  "additionalProperties": false,
  "required": ["schema_version", "query", "results", "attribution"],
  "properties": {
    "schema_version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" },
    "query": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200,
      "description": "The text that was looked up: the q parameter with its whitespace collapsed to single spaces and trimmed."
    },
    "results": {
      "type": "array",
      "maxItems": 10,
      "description": "Candidates in the order the answering geocoder ranked them, at most the requested limit. Every candidate comes from one geocoder; results are never merged across geocoders. Empty when every geocoder asked found nothing.",
      "items": { "$ref": "#/$defs/candidate" }
    },
    "attribution": {
      "type": "array",
      "minItems": 1,
      "description": "One line per geocoder whose answer this is, to be shown wherever the results are shown. The OpenStreetMap Nominatim line is required by its usage policy and the ODbL.",
      "items": { "type": "string", "minLength": 1 }
    }
  },
  "$defs": {
    "candidate": {
      "type": "object",
      "additionalProperties": false,
      "required": ["label", "lat", "lon", "precision", "country_code", "source"],
      "properties": {
        "label": {
          "type": "string",
          "minLength": 1,
          "description": "The geocoder's own name for what it matched, as it wrote it. Read this before choosing: it is the only way to tell two candidates of the same name apart."
        },
        "lat": { "type": "number", "minimum": -90, "maximum": 90, "description": "Latitude in decimal degrees (WGS 84), at most seven decimal places." },
        "lon": { "type": "number", "minimum": -180, "maximum": 180, "description": "Longitude in decimal degrees (WGS 84), at most seven decimal places." },
        "precision": {
          "type": "string",
          "enum": ["address", "street", "postcode", "locality", "region", "country"],
          "description": "What the point stands for, finest first. address: one building or address point (the US Census geocoder places it along the street segment's address range rather than on a rooftop). street: somewhere on a street or a small named place. postcode: the middle of a postal code area. locality: a town, city, neighbourhood or named square. region: a county, state or province. country: a whole country, or anything larger. Never finer than the geocoder's own ranking supports, and coarsened when the matched feature is larger than the precision would suggest."
        },
        "country_code": {
          "type": ["string", "null"],
          "pattern": "^[A-Z]{2}$",
          "description": "ISO 3166-1 alpha-2 code of the country the geocoder places the match in, upper case. Null when the geocoder names no country (open water, for one)."
        },
        "source": {
          "type": "string",
          "enum": ["us-census", "nominatim"],
          "description": "The geocoder that produced this candidate: us-census is the U.S. Census Bureau Geocoder, nominatim is OpenStreetMap Nominatim."
        }
      }
    }
  }
}
