{
  "openapi": "3.1.0",
  "info": {
    "title": "Darly API",
    "version": "1.0.0",
    "summary": "Public lidar elevation data through one API.",
    "description": "Darly serves normalized public lidar elevation data two ways: a standards-compliant STAC catalog (anonymous browsing allowed) and an Elevation API for point lookups, path sampling, and raster GeoTIFF extracts (free API key required). Elevations are always meters. Human docs: https://darly.io/docs — LLM-readable docs: https://darly.io/llms.txt",
    "contact": {
      "name": "Darly",
      "email": "contact@darly.io",
      "url": "https://darly.io/docs"
    }
  },
  "servers": [{ "url": "https://api.darly.io" }],
  "externalDocs": {
    "description": "Developer documentation",
    "url": "https://darly.io/docs"
  },
  "tags": [
    { "name": "Elevation", "description": "Point, batch, and path-sampled elevation lookups. Counted per point against per-minute and monthly lookup limits." },
    { "name": "Raster", "description": "DEM extracts as cloud-optimized GeoTIFFs. Response bytes count against the monthly data-out quota." },
    { "name": "STAC", "description": "SpatioTemporal Asset Catalog API — works with pystac-client, QGIS, GDAL, and STAC Browser. Anonymous access at 30 requests/min per IP; an API key raises limits." },
    { "name": "Downloads", "description": "Whole-file downloads of Darly-hosted source COGs (paid plans). Charged at full file size against the monthly data-out quota." },
    { "name": "Meta", "description": "Service health." }
  ],
  "paths": {
    "/v1/health": {
      "get": {
        "tags": ["Meta"],
        "operationId": "health",
        "summary": "Health check (no auth required)",
        "responses": {
          "200": {
            "description": "Service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "status": { "type": "string", "const": "OK" } }
                }
              }
            }
          }
        }
      }
    },
    "/v1/elevation/point": {
      "get": {
        "tags": ["Elevation"],
        "operationId": "elevationPoint",
        "summary": "Elevation at a single point",
        "description": "Returns the elevation at (lat, lon) from the finest-resolution dataset covering the point, falling through to coarser and finally global 30 m datasets on nodata. Costs 1 lookup.",
        "security": [{ "apiKeyHeader": [] }, { "apiKeyBearer": [] }],
        "parameters": [
          { "name": "lat", "in": "query", "required": true, "schema": { "type": "number", "minimum": -90, "maximum": 90 } },
          { "name": "lon", "in": "query", "required": true, "schema": { "type": "number", "minimum": -180, "maximum": 180 } },
          { "$ref": "#/components/parameters/surface" },
          { "$ref": "#/components/parameters/vertical_datum" },
          { "$ref": "#/components/parameters/interpolation" },
          { "$ref": "#/components/parameters/radius" },
          { "$ref": "#/components/parameters/collections" }
        ],
        "responses": {
          "200": {
            "description": "Elevation result. `elevation` and `collection` are null when no dataset has valid data at the point (e.g. open ocean).",
            "headers": { "$ref": "#/components/headers/rateLimitBundle" },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PointResponse" },
                "example": {
                  "status": "OK",
                  "result": {
                    "lat": 45.51,
                    "lon": -73.56,
                    "elevation": 36.42,
                    "collection": "can-nrcan-hrdem-1m",
                    "vertical_datum": "egm2008",
                    "surface": "dtm",
                    "resolution": 1.0
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/PlanForbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/elevation/points": {
      "get": {
        "tags": ["Elevation"],
        "operationId": "elevationPointsGet",
        "summary": "Batch elevation lookup",
        "description": "Elevations for up to 512 points (100 on Free). Each point costs 1 lookup against per-minute and monthly limits.",
        "security": [{ "apiKeyHeader": [] }, { "apiKeyBearer": [] }],
        "parameters": [
          { "name": "latlons", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Pipe-separated `lat,lon` pairs, e.g. `45.51,-73.56|49.28,-123.12`. Max 512 points (100 on Free).", "example": "45.51,-73.56|49.28,-123.12" },
          { "$ref": "#/components/parameters/surface" },
          { "$ref": "#/components/parameters/vertical_datum" },
          { "$ref": "#/components/parameters/interpolation" },
          { "$ref": "#/components/parameters/radius" },
          { "$ref": "#/components/parameters/collections" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/PointsOk" },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/PlanForbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "tags": ["Elevation"],
        "operationId": "elevationPointsPost",
        "summary": "Batch elevation lookup (JSON body)",
        "description": "Same as GET; parameters move to a JSON body (body wins over query on conflict). `latlons` may be a pipe-separated string, a list of `\"lat,lon\"` strings, or a list of `[lat, lon]` pairs.",
        "security": [{ "apiKeyHeader": [] }, { "apiKeyBearer": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["latlons"],
                "properties": {
                  "latlons": { "$ref": "#/components/schemas/LatLonList" },
                  "surface": { "$ref": "#/components/schemas/SurfaceOption" },
                  "vertical_datum": { "$ref": "#/components/schemas/DatumOption" },
                  "interpolation": { "$ref": "#/components/schemas/InterpolationOption" },
                  "radius": { "type": "number", "minimum": 0, "maximum": 100 },
                  "collections": { "$ref": "#/components/schemas/CollectionsList" }
                }
              },
              "example": { "latlons": [[45.51, -73.56], [49.28, -123.12], [19.43, -99.13]] }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/PointsOk" },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/PlanForbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/elevation/sample": {
      "get": {
        "tags": ["Elevation"],
        "operationId": "elevationSampleGet",
        "summary": "Elevations sampled along a path",
        "description": "Computes `samples` evenly spaced points along the geodesic polyline `path` (endpoints included), then behaves like /v1/elevation/points. Costs `samples` lookups. Useful for elevation profiles.",
        "security": [{ "apiKeyHeader": [] }, { "apiKeyBearer": [] }],
        "parameters": [
          { "name": "path", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Pipe-separated `lat,lon` polyline vertices (2–25).", "example": "46.85,-121.76|46.79,-121.74" },
          { "name": "samples", "in": "query", "required": true, "schema": { "type": "integer", "minimum": 2, "maximum": 512 }, "description": "Number of evenly spaced samples along the path (max 512; 100 on Free)." },
          { "$ref": "#/components/parameters/surface" },
          { "$ref": "#/components/parameters/vertical_datum" },
          { "$ref": "#/components/parameters/interpolation" },
          { "$ref": "#/components/parameters/radius" },
          { "$ref": "#/components/parameters/collections" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/PointsOk" },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/PlanForbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "tags": ["Elevation"],
        "operationId": "elevationSamplePost",
        "summary": "Elevations sampled along a path (JSON body)",
        "security": [{ "apiKeyHeader": [] }, { "apiKeyBearer": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["path", "samples"],
                "properties": {
                  "path": { "$ref": "#/components/schemas/LatLonList" },
                  "samples": { "type": "integer", "minimum": 2, "maximum": 512 },
                  "surface": { "$ref": "#/components/schemas/SurfaceOption" },
                  "vertical_datum": { "$ref": "#/components/schemas/DatumOption" },
                  "interpolation": { "$ref": "#/components/schemas/InterpolationOption" },
                  "radius": { "type": "number", "minimum": 0, "maximum": 100 },
                  "collections": { "$ref": "#/components/schemas/CollectionsList" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/PointsOk" },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/PlanForbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/elevation/raster": {
      "get": {
        "tags": ["Raster"],
        "operationId": "elevationRaster",
        "summary": "DEM extract as a cloud-optimized GeoTIFF",
        "description": "Clips a DEM over the bbox, composited finest-source-first (or in your `collections` order) with per-source vertical-datum normalization (no seam blending). Provide exactly ONE sizing spec: `width_px`+`height_px`, `resolution_m`, or `resolution_x`+`resolution_y`. Output: float32 meters (nodata -9999), or with `render=hillshade` a uint8 shaded-relief band (1–255, nodata 0). Caps: 2,500 px/side (512 on Free), 100 km bbox extent per side. A bbox with no coverage still returns 200 with an all-nodata raster. Response bytes are charged to the monthly data-out quota.",
        "security": [{ "apiKeyHeader": [] }, { "apiKeyBearer": [] }],
        "parameters": [
          { "name": "bbox_left", "in": "query", "required": true, "schema": { "type": "number" } },
          { "name": "bbox_bottom", "in": "query", "required": true, "schema": { "type": "number" } },
          { "name": "bbox_right", "in": "query", "required": true, "schema": { "type": "number" } },
          { "name": "bbox_top", "in": "query", "required": true, "schema": { "type": "number" } },
          { "name": "bbox_projection", "in": "query", "schema": { "type": "string", "default": "epsg:4326" }, "description": "CRS of the bbox parameters, `epsg:NNNN`." },
          { "name": "projection", "in": "query", "schema": { "type": "string", "default": "epsg:4326" }, "description": "Output raster CRS: `epsg:NNNN`, or `utm` (UTM zone of the bbox center)." },
          { "name": "width_px", "in": "query", "schema": { "type": "integer", "minimum": 10, "maximum": 2500 }, "description": "Output width. Pair with height_px; bounds match the bbox exactly." },
          { "name": "height_px", "in": "query", "schema": { "type": "integer", "minimum": 10, "maximum": 2500 } },
          { "name": "resolution_m", "in": "query", "schema": { "type": "number", "minimum": 0.5, "maximum": 1000 }, "description": "Pixel size in meters. Bounds buffered right/down to whole pixels." },
          { "name": "resolution_x", "in": "query", "schema": { "type": "number", "exclusiveMinimum": 0 }, "description": "Pixel size in output-projection units. Pair with resolution_y." },
          { "name": "resolution_y", "in": "query", "schema": { "type": "number", "exclusiveMinimum": 0 } },
          { "name": "source_mask", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Adds band 2 (source_index): per-pixel source collection, mapped by the darly-source-map GeoTIFF tag (0 = nodata). Not valid with render=hillshade." },
          { "name": "output_profile", "in": "query", "schema": { "type": "string", "enum": ["default", "zstd"], "default": "default" }, "description": "COG compression: DEFLATE, or ZSTD (smaller/faster, needs newer readers)." },
          { "name": "render", "in": "query", "schema": { "type": "string", "enum": ["elevation", "hillshade"], "default": "elevation" }, "description": "`elevation` (float32 meters) or `hillshade` (uint8 shaded relief replacing the elevation band; mutually exclusive with source_mask)." },
          { "name": "azimuth", "in": "query", "schema": { "type": "number", "minimum": 0, "maximum": 360, "default": 315 }, "description": "Hillshade sun azimuth in degrees (render=hillshade only)." },
          { "name": "altitude", "in": "query", "schema": { "type": "number", "minimum": 0, "maximum": 90, "default": 45 }, "description": "Hillshade sun altitude in degrees (render=hillshade only)." },
          { "name": "z_factor", "in": "query", "schema": { "type": "number", "exclusiveMinimum": 0, "maximum": 100, "default": 1 }, "description": "Hillshade vertical exaggeration (render=hillshade only)." },
          { "$ref": "#/components/parameters/surface" },
          { "$ref": "#/components/parameters/vertical_datum" },
          { "$ref": "#/components/parameters/interpolation" },
          { "$ref": "#/components/parameters/collections" }
        ],
        "responses": {
          "200": {
            "description": "Cloud-optimized GeoTIFF. Elevation: float32 meters, nodata -9999. Hillshade: uint8 (1–255), nodata 0. GeoTIFF tags: darly-collections, darly-surface, darly-render, plus darly-vertical-datum + darly-source-map (elevation) or darly-hillshade (hillshade).",
            "headers": {
              "x-data-sources": { "description": "Comma-separated collection IDs in composite order.", "schema": { "type": "string" } },
              "content-disposition": { "schema": { "type": "string" } }
            },
            "content": {
              "image/tiff; application=geotiff; profile=cloud-optimized": {
                "schema": { "type": "string", "format": "binary" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/PlanForbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/search": {
      "get": {
        "tags": ["STAC"],
        "operationId": "stacSearchGet",
        "summary": "STAC item search",
        "description": "Standard STAC API item search. Anonymous access allowed (30 req/min per IP); an API key raises limits. Page size caps at 100; follow the `rel: next` link to paginate.",
        "security": [{}, { "apiKeyHeader": [] }, { "apiKeyBearer": [] }],
        "parameters": [
          { "name": "collections", "in": "query", "schema": { "type": "string" }, "description": "Comma-separated collection IDs." },
          { "name": "bbox", "in": "query", "schema": { "type": "string" }, "description": "`minLon,minLat,maxLon,maxLat` (WGS84)." },
          { "name": "datetime", "in": "query", "schema": { "type": "string" }, "description": "RFC 3339 instant or `start/end` interval; open ends with `..`." },
          { "name": "ids", "in": "query", "schema": { "type": "string" }, "description": "Comma-separated item IDs." },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10 } }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ItemCollection" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "tags": ["STAC"],
        "operationId": "stacSearchPost",
        "summary": "STAC item search (JSON body)",
        "description": "Same as GET /search with the standard STAC search body; also supports `intersects` (GeoJSON geometry).",
        "security": [{}, { "apiKeyHeader": [] }, { "apiKeyBearer": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "collections": { "type": "array", "items": { "type": "string" } },
                  "bbox": { "type": "array", "items": { "type": "number" }, "minItems": 4, "maxItems": 6 },
                  "intersects": { "type": "object", "description": "GeoJSON geometry." },
                  "datetime": { "type": "string" },
                  "ids": { "type": "array", "items": { "type": "string" } },
                  "limit": { "type": "integer", "minimum": 1, "maximum": 100 }
                }
              },
              "example": {
                "collections": ["usa-usgs-3dep-1m-seamless"],
                "bbox": [-122.52, 37.7, -122.35, 37.83],
                "limit": 10
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/ItemCollection" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/collections": {
      "get": {
        "tags": ["STAC"],
        "operationId": "stacCollections",
        "summary": "List collections",
        "security": [{}, { "apiKeyHeader": [] }, { "apiKeyBearer": [] }],
        "responses": {
          "200": {
            "description": "All collections available to the caller. See https://darly.io/docs/coverage for the current list.",
            "content": { "application/json": { "schema": { "type": "object", "description": "STAC Collections response (`collections` array + `links`)." } } }
          },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/collections/{collection_id}": {
      "get": {
        "tags": ["STAC"],
        "operationId": "stacCollection",
        "summary": "Collection metadata",
        "security": [{}, { "apiKeyHeader": [] }, { "apiKeyBearer": [] }],
        "parameters": [{ "$ref": "#/components/parameters/collectionId" }],
        "responses": {
          "200": { "description": "STAC Collection.", "content": { "application/json": { "schema": { "type": "object" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/collections/{collection_id}/items": {
      "get": {
        "tags": ["STAC"],
        "operationId": "stacItems",
        "summary": "List a collection's items",
        "security": [{}, { "apiKeyHeader": [] }, { "apiKeyBearer": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/collectionId" },
          { "name": "bbox", "in": "query", "schema": { "type": "string" } },
          { "name": "datetime", "in": "query", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10 } }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ItemCollection" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/collections/{collection_id}/items/{item_id}": {
      "get": {
        "tags": ["STAC"],
        "operationId": "stacItem",
        "summary": "One item with full asset links",
        "description": "Single-item responses are where Darly-hosted assets carry /dl download links (paid plans). Elevation raster assets use the keys `dtm` and/or `dsm` with projection metadata per asset.",
        "security": [{}, { "apiKeyHeader": [] }, { "apiKeyBearer": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/collectionId" },
          { "name": "item_id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "STAC Item (GeoJSON Feature).", "content": { "application/geo+json": { "schema": { "type": "object" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/dl/{collection_id}/{item_id}/{asset_key}": {
      "get": {
        "tags": ["Downloads"],
        "operationId": "downloadAsset",
        "summary": "Download a Darly-hosted source COG (paid plans)",
        "description": "Authenticates, pre-charges the file's full size against the monthly data-out quota, and redirects (302) to a signed URL valid ~5 minutes. Repeat requests for the same asset within 15 minutes redirect without a second charge. HEAD charges like GET. Links are embedded in single-item STAC responses — construct them from there, not by hand.",
        "security": [{ "apiKeyHeader": [] }, { "apiKeyBearer": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/collectionId" },
          { "name": "item_id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "asset_key", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Asset key from the item, e.g. `dtm` or `dsm`." }
        ],
        "responses": {
          "302": {
            "description": "Redirect to a short-lived signed URL for the file bytes.",
            "headers": { "Location": { "schema": { "type": "string", "format": "uri" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/PlanForbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Create keys at https://darly.io/dashboard/keys (free)."
      },
      "apiKeyBearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "The API key as a bearer token."
      }
    },
    "parameters": {
      "collectionId": {
        "name": "collection_id",
        "in": "path",
        "required": true,
        "schema": { "type": "string" },
        "description": "Collection ID, e.g. `usa-usgs-3dep-1m-seamless`. Unknown and not-entitled collections both 404.",
        "example": "usa-usgs-3dep-1m-seamless"
      },
      "surface": {
        "name": "surface",
        "in": "query",
        "schema": { "$ref": "#/components/schemas/SurfaceOption" },
        "description": "dtm = terrain (bare earth), dsm = surface (buildings/canopy). Only collections carrying the requested asset are considered."
      },
      "vertical_datum": {
        "name": "vertical_datum",
        "in": "query",
        "schema": { "$ref": "#/components/schemas/DatumOption" },
        "description": "Output vertical datum: egm2008 = orthometric height above the EGM2008 geoid (default); wgs84_ellipsoid = height above the WGS84 ellipsoid (raw GPS heights, ~2 m realization ambiguity)."
      },
      "interpolation": {
        "name": "interpolation",
        "in": "query",
        "schema": { "$ref": "#/components/schemas/InterpolationOption" },
        "description": "Ignored when radius > 0. Bilinear degrades to nearest at raster edges and nodata neighbors."
      },
      "radius": {
        "name": "radius",
        "in": "query",
        "schema": { "type": "number", "minimum": 0, "maximum": 100, "default": 0 },
        "description": "Meters. 0 samples at the point; otherwise the mean of valid cells whose center falls within the circle."
      },
      "collections": {
        "name": "collections",
        "in": "query",
        "schema": { "type": "string" },
        "description": "Comma-separated STAC collection IDs (as listed by GET /collections) in priority order: the first collection answers wherever it has valid data, later ones only fill its gaps. Only listed collections are considered. Omitted: all allowed collections ranked finest resolution first. Unknown and not-allowed IDs return a 400."
      }
    },
    "headers": {
      "rateLimitBundle": {
        "description": "Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix seconds) for the limiter class that applied.",
        "schema": { "type": "integer" }
      }
    },
    "schemas": {
      "SurfaceOption": { "type": "string", "enum": ["dtm", "dsm"], "default": "dtm" },
      "DatumOption": { "type": "string", "enum": ["egm2008", "wgs84_ellipsoid"], "default": "egm2008" },
      "InterpolationOption": { "type": "string", "enum": ["bilinear", "nearest"], "default": "bilinear" },
      "CollectionsList": {
        "description": "STAC collection IDs (as listed by GET /collections) in priority order (first wins, later ones fill gaps): a comma-separated string or a list of IDs.",
        "oneOf": [
          { "type": "string" },
          { "type": "array", "items": { "type": "string" } }
        ]
      },
      "LatLonList": {
        "description": "A pipe-separated `lat,lon|lat,lon` string, a list of `\"lat,lon\"` strings, or a list of [lat, lon] number pairs.",
        "oneOf": [
          { "type": "string" },
          { "type": "array", "items": { "type": "string" } },
          { "type": "array", "items": { "type": "array", "items": { "type": "number" }, "minItems": 2, "maxItems": 2 } }
        ]
      },
      "PointResult": {
        "type": "object",
        "properties": {
          "lat": { "type": "number" },
          "lon": { "type": "number" },
          "elevation": { "type": ["number", "null"], "description": "Meters in the requested vertical datum; null when no dataset has valid data here." },
          "collection": { "type": ["string", "null"], "description": "Collection that answered." },
          "vertical_datum": { "$ref": "#/components/schemas/DatumOption" },
          "surface": { "$ref": "#/components/schemas/SurfaceOption" },
          "resolution": { "type": ["number", "null"], "description": "Source ground sampling distance in meters." }
        }
      },
      "PointResponse": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "const": "OK" },
          "result": { "$ref": "#/components/schemas/PointResult" }
        }
      },
      "PointsResponse": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "const": "OK" },
          "results": { "type": "array", "items": { "$ref": "#/components/schemas/PointResult" }, "description": "Same order as the input points." }
        }
      },
      "ValidationError": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "const": "INVALID_REQUEST" },
          "error": { "type": "string" }
        }
      },
      "DetailError": {
        "type": "object",
        "properties": { "detail": { "type": "string" } }
      }
    },
    "responses": {
      "PointsOk": {
        "description": "Elevation results in input order. Points with no valid data anywhere have elevation: null.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PointsResponse" } } }
      },
      "ItemCollection": {
        "description": "STAC ItemCollection (GeoJSON FeatureCollection) with pagination links (`rel: next`).",
        "content": { "application/geo+json": { "schema": { "type": "object" } } }
      },
      "InvalidRequest": {
        "description": "Invalid parameters.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ValidationError" }, "example": { "status": "INVALID_REQUEST", "error": "lat must be between -90 and 90" } } }
      },
      "Unauthorized": {
        "description": "Missing authentication (invalid API keys behave like missing keys).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DetailError" }, "example": { "detail": "Authentication is required for the elevation API" } } }
      },
      "PlanForbidden": {
        "description": "The account's plan does not include this endpoint.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DetailError" }, "example": { "detail": "The current plan does not include this endpoint" } } }
      },
      "NotFound": {
        "description": "Unknown route, collection, item, or asset — including collections outside the caller's plan.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DetailError" }, "example": { "detail": "Not found" } } }
      },
      "RateLimited": {
        "description": "Per-minute limit hit (retry after X-RateLimit-Reset) or a monthly quota is exhausted.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/DetailError" },
            "examples": {
              "perMinute": { "value": { "detail": "Rate limit exceeded" } },
              "monthlyLookups": { "value": { "detail": "Monthly lookup quota exceeded" } },
              "monthlyDataOut": { "value": { "detail": "Monthly data-out quota exceeded" } }
            }
          }
        }
      }
    }
  }
}
