{
  "openapi": "3.1.0",
  "info": {
    "title": "Mantal API",
    "version": "0.1.0",
    "description": "Unified API for Swedish business & civil-registry data.\n\n- **Identity** — `/v1/persons/*` backed by SPAR (Sveriges Statens personadressregister) over mTLS.\n- **Geography** — `/v1/geo/*` over a curated geographic master dataset (regions / municipalities / localities / postal codes), using the OpenStreetMap `admin_level` model internally and the Pelias address hierarchy externally.\n\nAll write/billable endpoints require `Authorization: Bearer pk_live_…` or `pk_test_…`.",
    "contact": { "name": "DEVDASH AB", "email": "philip@devdash.co" },
    "license": { "name": "Proprietary" }
  },
  "servers": [
    { "url": "https://mantal.se", "description": "Production (canonical while mantal.eu nameservers delegate)" },
    { "url": "https://mantal.eu", "description": "Production (planned canonical)" }
  ],
  "security": [{ "BearerAuth": [] }],
  "tags": [
    { "name": "platform", "description": "Health, docs, billing" },
    { "name": "persons", "description": "SPAR-backed person lookup (real-time, billed by SPAR's tiered pricing)" },
    { "name": "geo:admin", "description": "Universal admin-area hierarchy (countries → regions → counties → localities → boroughs → neighbourhoods)" },
    { "name": "geo:places", "description": "Statistical / populated places (tätort, RegSO, OSM city/town/village)" },
    { "name": "geo:postal", "description": "Postal codes (orthogonal axis to admin hierarchy)" },
    { "name": "geo:lookup", "description": "Search, autocomplete, reverse geocoding" },
    { "name": "tiles", "description": "Self-hosted vector map tiles + MapLibre styles (LocationIQ-style)" }
  ],

  "paths": {
    "/health": {
      "get": {
        "tags": ["platform"],
        "summary": "Liveness probe",
        "description": "Returns current SPAR environment and today's call counts. No auth.",
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Health" } } }
          }
        }
      }
    },

    "/openapi.json": {
      "get": {
        "tags": ["platform"], "summary": "This spec", "security": [],
        "responses": { "200": { "description": "OpenAPI 3.1 spec" } }
      }
    },

    "/v1/admin/billing": {
      "get": {
        "tags": ["platform"],
        "summary": "Real-time SPAR cost",
        "description": "Computes today / month / year cost from the local `usage_log`, applying SPAR's tiered pricing. SPAR has no programmatic billing API — this is our ground truth. Reconcile against the monthly SPAR invoice; expect <1% drift.",
        "security": [{ "AdminKey": [] }],
        "parameters": [{ "name": "env", "in": "query", "schema": { "type": "string", "enum": ["test", "prod"] }, "description": "Override the configured SPAR env" }],
        "responses": {
          "200": { "description": "Cost summary", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Billing" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "503": { "description": "ADMIN_KEY not configured" }
        }
      }
    },

    "/v1/tiles/preview": {
      "get": {
        "tags": ["tiles"],
        "summary": "Live MapLibre preview page",
        "description": "Self-contained HTML page rendering the Mantal basemap with MapLibre GL JS. Hot-toggle light/dark theme; fly-to presets for Stockholm / Göteborg / Malmö / Kiruna. Useful for visual smoke-tests and customer demos.",
        "security": [],
        "responses": {
          "200": { "description": "HTML page", "content": { "text/html": { "schema": { "type": "string" } } } }
        }
      }
    },

    "/v1/tiles/styles": {
      "get": {
        "tags": ["tiles"],
        "summary": "List available MapLibre styles",
        "description": "Returns the public MapLibre/Mapbox-GL JS styles bundled with the API. Each style references the vector-tile endpoint at `tiles.mantal.{se,eu}` (Protomaps Basemaps v4 schema, OpenStreetMap data).",
        "security": [],
        "responses": {
          "200": {
            "description": "Style index",
            "content": { "application/json": { "schema": {
              "type": "object",
              "properties": {
                "ok": { "type": "boolean" },
                "styles": { "type": "array", "items": { "type": "object", "properties": {
                  "name": { "type": "string", "example": "mantal-light" },
                  "url": { "type": "string", "format": "uri" },
                  "theme": { "type": "string", "enum": ["light", "dark"] }
                } } },
                "tiles_endpoint": { "type": "string", "example": "https://tiles.mantal.se/mantal/{z}/{x}/{y}.mvt" }
              }
            } } }
          }
        }
      }
    },

    "/v1/tiles/styles/{name}.json": {
      "get": {
        "tags": ["tiles"],
        "summary": "Fetch a MapLibre style JSON",
        "description": "Returns a [Mapbox GL JS style spec](https://maplibre.org/maplibre-style-spec/) document. Pass it directly to `new maplibregl.Map({ style: 'https://mantal.se/v1/tiles/styles/mantal-light.json' })`.",
        "security": [],
        "parameters": [
          { "name": "name", "in": "path", "required": true, "schema": { "type": "string", "enum": ["mantal-light", "mantal-dark"] } }
        ],
        "responses": {
          "200": { "description": "Style JSON", "content": { "application/json": { "schema": { "type": "object", "description": "MapLibre style spec" } } } },
          "404": { "description": "Unknown style" }
        }
      }
    },

    "/mantal/{z}/{x}/{y}.mvt": {
      "get": {
        "tags": ["tiles"],
        "summary": "Fetch a vector tile",
        "description": "Served by the tile container at `tiles.mantal.se` / `tiles.mantal.eu` (NOT mantal.se). Returns a [Mapbox Vector Tile](https://github.com/mapbox/vector-tile-spec/tree/master/2.1) (protobuf) following the [Protomaps Basemaps v4](https://github.com/protomaps/basemaps) layer schema (`earth`, `landuse`, `water`, `roads`, `boundaries`, `buildings`, `places`, `physical_line`).\n\nData source: OpenStreetMap via Planetiler. Updated periodically.",
        "security": [],
        "servers": [
          { "url": "https://tiles.mantal.se", "description": "Tile server (canonical while mantal.eu nameservers delegate)" },
          { "url": "https://tiles.mantal.eu", "description": "Tile server (planned canonical)" }
        ],
        "parameters": [
          { "name": "z", "in": "path", "required": true, "schema": { "type": "integer", "minimum": 0, "maximum": 15 }, "description": "Zoom level (0=world, 14-15=building detail)" },
          { "name": "x", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "Tile column" },
          { "name": "y", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "Tile row" }
        ],
        "responses": {
          "200": { "description": "Vector tile", "content": { "application/x-protobuf": { "schema": { "type": "string", "format": "binary" } } } },
          "204": { "description": "Empty tile (valid coordinates, no features)" },
          "404": { "description": "Tile outside dataset bounds or invalid coordinates" }
        }
      }
    },

    "/v1/persons/lookup": {
      "post": {
        "tags": ["persons"],
        "summary": "Look up a person by personnummer",
        "description": "PNR → person details, backed by SPAR Personsökning 2023.1. Cache-first (24h TTL); cached responses are NOT billed.\n\nResponse projection controlled by `shape`:\n- `rich` (default) — nested SPAR Person object with all extracted fields\n- `flat` — wide attribute shape (`name1`, `street`, `postalcode`, ...) optionally filtered via `fields`",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PersonLookupRequest" }, "examples": {
            "rich": { "summary": "Default rich shape", "value": { "pnr": "195704133106" } },
            "flat-filtered": { "summary": "Flat shape with field filter", "value": { "pnr": "195704133106", "shape": "flat", "fields": ["name1", "name3", "street", "postalcode", "city"] } }
          } } }
        },
        "parameters": [{ "name": "nocache", "in": "query", "schema": { "type": "string", "enum": ["1"] }, "description": "Bypass the person cache for this lookup" }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PersonLookupResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "description": "SPAR daily cap reached" },
          "502": { "description": "SPAR upstream error (SOAP fault or HTTP error)" }
        }
      }
    },

    "/v1/persons/search": {
      "post": {
        "tags": ["persons"],
        "summary": "Search persons by name + address (SPAR Sök Namn)",
        "description": "Returns a `searchId` plus result count. Use `GET /v1/persons/search/{searchId}` to retrieve the cached hits (24h TTL). Each hit is also cached as a `person_cache` row → subsequent `/lookup` calls on the same PNRs are free.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PersonSearchRequest" }, "examples": {
            "name-and-city": { "value": { "fornamn": "Anna", "mellanEfternamn": "Andersson", "postort": "Stockholm" } },
            "wildcard": { "value": { "namn": "A*", "kommunKod": "0180" } }
          } } }
        },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PersonSearchResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },

    "/v1/persons/search/{searchId}": {
      "get": {
        "tags": ["persons"],
        "summary": "Retrieve cached search results",
        "parameters": [
          { "name": "searchId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } },
          { "name": "shape", "in": "query", "schema": { "type": "string", "enum": ["rich", "flat"], "default": "rich" } },
          { "name": "fields", "in": "query", "schema": { "type": "string" }, "description": "Comma-separated subset of fields (flat shape only)" }
        ],
        "responses": {
          "200": { "description": "OK" },
          "404": { "description": "searchId unknown or expired" }
        }
      }
    },

    "/v1/geo/admin/{id}": {
      "get": {
        "tags": ["geo:admin"],
        "summary": "Get an admin area by id (any layer)",
        "description": "Returns the entity + its Pelias context (country → region → county → locality → borough → neighbourhood).",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "examples": {
          "country": { "value": "se" },
          "region": { "value": "se:region:stockholms-lan" },
          "locality": { "value": "se:locality:stockholm" },
          "borough": { "value": "se:borough:stockholm:sodermalm" }
        } }],
        "responses": {
          "200": { "description": "Admin area with context", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminAreaWithContext" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },

    "/v1/geo/admin/{id}/children": {
      "get": {
        "tags": ["geo:admin"],
        "summary": "Direct children one Pelias layer below the given admin",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "$ref": "#/components/parameters/Limit" }, { "$ref": "#/components/parameters/Offset" }
        ],
        "responses": { "200": { "description": "Children list" } }
      }
    },

    "/v1/geo/admin/{id}/parents": {
      "get": {
        "tags": ["geo:admin"],
        "summary": "Ancestor chain (root → self exclusive)",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Ancestors ordered root → leaf" } }
      }
    },

    "/v1/geo/admin": {
      "get": {
        "tags": ["geo:admin"],
        "summary": "List admin areas with filters",
        "parameters": [
          { "name": "country", "in": "query", "schema": { "type": "string", "minLength": 2, "maxLength": 2 } },
          { "name": "layer", "in": "query", "schema": { "type": "string", "enum": ["country", "region", "county", "locality", "borough", "neighbourhood"] } },
          { "name": "parent", "in": "query", "schema": { "type": "string" } },
          { "$ref": "#/components/parameters/Limit" }, { "$ref": "#/components/parameters/Offset" }
        ],
        "responses": { "200": { "description": "Filtered admin areas" } }
      }
    },

    "/v1/geo/countries":     { "get": { "tags": ["geo:admin"], "summary": "List countries served", "parameters": [{ "name": "enabled", "in": "query", "schema": { "type": "string", "enum": ["true"] } }], "responses": { "200": { "description": "Country list" } } } },
    "/v1/geo/countries/{code}": { "get": { "tags": ["geo:admin"], "summary": "Country metadata", "parameters": [{ "name": "code", "in": "path", "required": true, "schema": { "type": "string", "minLength": 2, "maxLength": 2 }, "example": "SE" }], "responses": { "200": { "description": "Country" }, "404": { "$ref": "#/components/responses/NotFound" } } } },

    "/v1/geo/regions":        { "get": { "tags": ["geo:admin"], "summary": "List regions (län / Bundesland / Région)", "parameters": [{ "$ref": "#/components/parameters/Country" }], "responses": { "200": { "description": "Region list" } } } },
    "/v1/geo/regions/{slug}": { "get": { "tags": ["geo:admin"], "summary": "Region by slug", "parameters": [{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" }, "example": "stockholms-lan" }, { "$ref": "#/components/parameters/Country" }], "responses": { "200": { "description": "Region + context" }, "404": { "$ref": "#/components/responses/NotFound" } } } },

    "/v1/geo/counties":        { "get": { "tags": ["geo:admin"], "summary": "List counties (Landkreis / Département) — none in SE", "parameters": [{ "$ref": "#/components/parameters/Country" }], "responses": { "200": { "description": "County list" } } } },
    "/v1/geo/counties/{slug}": { "get": { "tags": ["geo:admin"], "summary": "County by slug", "parameters": [{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/Country" }], "responses": { "200": { "description": "County" }, "404": { "$ref": "#/components/responses/NotFound" } } } },

    "/v1/geo/localities":        { "get": { "tags": ["geo:admin"], "summary": "List localities (kommun / Gemeinde / Commune)", "parameters": [{ "$ref": "#/components/parameters/Country" }], "responses": { "200": { "description": "Locality list" } } } },
    "/v1/geo/localities/{slug}": { "get": { "tags": ["geo:admin"], "summary": "Locality by slug", "parameters": [{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" }, "example": "stockholm" }, { "$ref": "#/components/parameters/Country" }], "responses": { "200": { "description": "Locality" }, "404": { "$ref": "#/components/responses/NotFound" } } } },

    "/v1/geo/boroughs":        { "get": { "tags": ["geo:admin"], "summary": "List boroughs (stadsdelsområde / Stadtbezirk)", "parameters": [{ "$ref": "#/components/parameters/Country" }], "responses": { "200": { "description": "Borough list" } } } },
    "/v1/geo/boroughs/{slug}": { "get": { "tags": ["geo:admin"], "summary": "Borough by slug", "parameters": [{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/Country" }], "responses": { "200": { "description": "Borough" }, "404": { "$ref": "#/components/responses/NotFound" } } } },

    "/v1/geo/neighbourhoods":        { "get": { "tags": ["geo:admin"], "summary": "List neighbourhoods", "responses": { "200": { "description": "Neighbourhood list" } } } },
    "/v1/geo/neighbourhoods/{slug}": { "get": { "tags": ["geo:admin"], "summary": "Neighbourhood by slug", "parameters": [{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "Neighbourhood" }, "404": { "$ref": "#/components/responses/NotFound" } } } },

    "/v1/geo/places": {
      "get": {
        "tags": ["geo:places"],
        "summary": "List statistical / populated places",
        "parameters": [
          { "$ref": "#/components/parameters/Country" },
          { "name": "type", "in": "query", "schema": { "type": "string" }, "examples": { "tatort": { "value": "tatort" }, "regso": { "value": "regso" } } },
          { "name": "parent", "in": "query", "schema": { "type": "string" }, "description": "parent_admin_id, e.g. se:locality:stockholm" }
        ],
        "responses": { "200": { "description": "Places" } }
      }
    },
    "/v1/geo/places/{id}": { "get": { "tags": ["geo:places"], "summary": "Place by id", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "example": "se:tatort:stockholm:stockholm" }], "responses": { "200": { "description": "Place + context" }, "404": { "$ref": "#/components/responses/NotFound" } } } },

    "/v1/geo/postal-codes": { "get": { "tags": ["geo:postal"], "summary": "List postal codes", "parameters": [{ "$ref": "#/components/parameters/Country" }, { "name": "admin", "in": "query", "schema": { "type": "string" }, "description": "Filter by primary_admin_area_id" }], "responses": { "200": { "description": "Postal codes" } } } },
    "/v1/geo/postal-codes/{code}": { "get": { "tags": ["geo:postal"], "summary": "Postal code with streets + context", "parameters": [{ "name": "code", "in": "path", "required": true, "schema": { "type": "string" }, "example": "11146" }, { "$ref": "#/components/parameters/Country" }], "responses": { "200": { "description": "Postal code" }, "404": { "$ref": "#/components/responses/NotFound" } } } },

    "/v1/geo/search": {
      "get": {
        "tags": ["geo:lookup"],
        "summary": "Typo-tolerant search across admin areas + places",
        "description": "Uses pg_trgm trigram similarity. Returns hits ordered by similarity score.",
        "parameters": [
          { "name": "q", "in": "query", "required": true, "schema": { "type": "string", "minLength": 2 }, "example": "stockh" },
          { "name": "layer", "in": "query", "schema": { "type": "string", "enum": ["country", "region", "county", "locality", "borough", "neighbourhood", "place"] } },
          { "$ref": "#/components/parameters/Country" },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } }
        ],
        "responses": { "200": { "description": "Search hits", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SearchHits" } } } } }
      }
    },

    "/v1/geo/autocomplete": {
      "get": {
        "tags": ["geo:lookup"],
        "summary": "Typeahead predictions",
        "parameters": [
          { "name": "q", "in": "query", "required": true, "schema": { "type": "string", "minLength": 2 } },
          { "name": "layers", "in": "query", "schema": { "type": "string" }, "description": "Comma-separated layers (default: country,region,locality,borough,place)" },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 8, "maximum": 20 } }
        ],
        "responses": { "200": { "description": "Predictions" } }
      }
    },

    "/v1/geo/reverse": {
      "get": {
        "tags": ["geo:lookup"],
        "summary": "Reverse geocoding (coords → Pelias context)",
        "description": "PostGIS ST_Contains across admin polygons + nearest postal code via ST_Distance. Returns full Pelias address-hierarchy context (country → region → county → locality → borough → neighbourhood) for the smallest containing admin area.",
        "parameters": [
          { "name": "lat", "in": "query", "required": true, "schema": { "type": "number", "format": "float" }, "example": 59.3326 },
          { "name": "lng", "in": "query", "required": true, "schema": { "type": "number", "format": "float" }, "example": 18.0649 }
        ],
        "responses": {
          "200": { "description": "Reverse geocode result", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReverseResult" } } } },
          "400": { "description": "Invalid or out-of-bounds coordinates" },
          "503": { "description": "PostGIS unavailable (geometry columns not populated for the queried point)" }
        }
      }
    },

    "/v1/geo/stats": {
      "get": {
        "tags": ["geo:lookup"],
        "summary": "Dataset counts per Pelias layer",
        "responses": { "200": { "description": "Counts", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Stats" } } } } }
      }
    }
  },

  "components": {
    "securitySchemes": {
      "BearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "pk_live_… / pk_test_…", "description": "Mint a key via `pnpm create-key <customer_id> [--plan pro] [--test]` on the server" },
      "AdminKey": { "type": "apiKey", "in": "header", "name": "X-Admin-Key", "description": "Server-side admin key for billing endpoint" }
    },
    "parameters": {
      "Limit": { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 100, "maximum": 1000 } },
      "Offset": { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0 } },
      "Country": { "name": "country", "in": "query", "schema": { "type": "string", "minLength": 2, "maxLength": 2 }, "description": "ISO 3166-1 alpha-2 (defaults to SE on slug lookups)" }
    },
    "responses": {
      "BadRequest":   { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": { "description": "Missing / invalid Bearer token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound":     { "description": "Resource not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    },
    "schemas": {
      "Error": { "type": "object", "properties": { "error": { "type": "string" }, "message": { "type": "string" } } },
      "Health": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "sparEnv": { "type": "string", "enum": ["test", "prod"] },
          "callsTodayProd": { "type": "integer" },
          "callsTodayTest": { "type": "integer" },
          "dailyCap": { "type": ["integer", "null"] }
        }
      },
      "Billing": {
        "type": "object",
        "properties": {
          "sparEnv": { "type": "string" },
          "today":      { "$ref": "#/components/schemas/BillingPeriod" },
          "this_month": { "$ref": "#/components/schemas/BillingPeriod" },
          "this_year":  { "$ref": "#/components/schemas/BillingPeriod" }
        }
      },
      "BillingPeriod": {
        "type": "object",
        "properties": {
          "persnr_calls": { "type": "integer" },
          "namn_calls":   { "type": "integer" },
          "persnr_cost_sek": { "type": "number" },
          "namn_cost_sek":   { "type": "number" },
          "total_cost_sek":  { "type": "number" },
          "next_persnr_call_price_sek": { "type": "number", "description": "Marginal price for the very next SPAR persnr call today (tier-based)" }
        }
      },
      "PersonLookupRequest": {
        "type": "object",
        "required": ["pnr"],
        "properties": {
          "pnr":   { "type": "string", "description": "10 or 12 digit Swedish personnummer (with or without dash)", "example": "195704133106" },
          "shape": { "type": "string", "enum": ["rich", "flat"], "default": "rich" },
          "fields": { "type": "array", "items": { "type": "string" }, "description": "Flat-shape projection filter (e.g. ['name1','street','postalcode','city'])" }
        }
      },
      "PersonLookupResponse": {
        "type": "object",
        "properties": {
          "ok":     { "type": "boolean" },
          "cached": { "type": "boolean" },
          "uuid":   { "type": ["string", "null"], "description": "SPAR's UUID for this call (for their audit trail)" },
          "person": { "description": "Either a rich Person object or a flat key/value map depending on `shape`" }
        }
      },
      "PersonSearchRequest": {
        "type": "object",
        "description": "Mix-and-match — at least one of (`namn`, `fornamn`, `mellanEfternamn`) is required (SPAR rejects pure-address searches).",
        "properties": {
          "namn":    { "type": "string", "description": "Full-name search (wildcard '*' allowed)" },
          "fornamn": { "type": "string" },
          "mellanEfternamn": { "type": "string", "description": "Surname OR middle name (SPAR's MellanEfternamnSokArgument)" },
          "utdelningsadress": { "type": "string" },
          "postort":    { "type": "string" },
          "postnummer": { "type": "string" },
          "yearFrom":   { "type": "string" },
          "yearTo":     { "type": "string" },
          "kon":        { "type": "string", "enum": ["K", "M"] },
          "lanKod":     { "type": "string" },
          "kommunKod":  { "type": "string" },
          "distriktKod":{ "type": "string" },
          "fonetiskSokning": { "type": "boolean" }
        }
      },
      "PersonSearchResponse": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "searchId":        { "type": "string", "format": "uuid" },
          "numberOfResults": { "type": "integer" },
          "truncatedAt100":  { "type": "boolean" }
        }
      },
      "PeliasLayer": { "type": "string", "enum": ["country", "region", "county", "locality", "borough", "neighbourhood"] },
      "AdminArea": {
        "type": "object",
        "properties": {
          "id":           { "type": "string", "example": "se:locality:stockholm" },
          "countryCode":  { "type": "string", "example": "SE" },
          "adminLevel":   { "type": "integer", "description": "OSM admin_level (2-11)" },
          "peliasLayer":  { "$ref": "#/components/schemas/PeliasLayer" },
          "parentId":     { "type": ["string", "null"] },
          "slug":         { "type": "string" },
          "name":         { "type": "string" },
          "nameEn":       { "type": ["string", "null"] },
          "levelLabel":   { "type": ["string", "null"], "description": "Country-specific label, e.g. 'Län' / 'Bundesland' / 'Région'" },
          "code":         { "type": ["string", "null"] },
          "iso31662":     { "type": ["string", "null"] },
          "nutsCode":     { "type": ["string", "null"] },
          "center":       { "type": ["object", "null"] },
          "boundingbox":  { "type": ["array", "null"] },
          "population":   { "type": ["integer", "null"] },
          "areaKm2":      { "type": ["number", "null"] }
        }
      },
      "PeliasContext": {
        "type": "object",
        "description": "Pelias address hierarchy — every layer is present (null if not applicable in the country).",
        "properties": {
          "country":       { "type": ["object", "null"] },
          "region":        { "type": ["object", "null"] },
          "county":        { "type": ["object", "null"] },
          "locality":      { "type": ["object", "null"] },
          "borough":       { "type": ["object", "null"] },
          "neighbourhood": { "type": ["object", "null"] }
        }
      },
      "AdminAreaWithContext": {
        "type": "object",
        "properties": {
          "ok":      { "type": "boolean" },
          "admin":   { "$ref": "#/components/schemas/AdminArea" },
          "context": { "$ref": "#/components/schemas/PeliasContext" }
        }
      },
      "SearchHits": {
        "type": "object",
        "properties": {
          "ok":      { "type": "boolean" },
          "query":   { "type": "string" },
          "count":   { "type": "integer" },
          "results": { "type": "array", "items": { "type": "object" } }
        }
      },
      "ReverseResult": {
        "type": "object",
        "properties": {
          "ok":  { "type": "boolean" },
          "lat": { "type": "number" },
          "lng": { "type": "number" },
          "context": { "$ref": "#/components/schemas/PeliasContext" },
          "matchedAdminId":  { "type": ["string", "null"] },
          "places":          { "type": "array", "items": { "type": "object" } },
          "nearestPostalCode": { "type": ["object", "null"] }
        }
      },
      "Stats": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "stats": {
            "type": "object",
            "properties": {
              "countries":     { "type": "integer", "example": 19 },
              "regions":       { "type": "integer", "example": 280 },
              "counties":      { "type": "integer", "example": 0 },
              "localities":    { "type": "integer", "example": 77657 },
              "boroughs":      { "type": "integer", "example": 107 },
              "places":        { "type": "integer", "example": 4800 },
              "postalCodes":   { "type": "integer", "example": 93603 }
            }
          }
        }
      }
    }
  }
}
