{
  "openapi": "3.0.3",
  "info": {
    "title": "3dtiled tile-server",
    "description": "A \"TiTiler for 3D\": live `<format>` → OGC 3D Tiles middleware. CORS-open, no auth, reads public sources only. Every endpoint streams/transcodes on demand — nothing is pre-baked unless you ask for `cache=full` (`/convert`).\n\nSee the [playground](/playground) to try requests interactively, or the [project docs](https://3dtiled-docs.prod.heritagewatch.ai) for the full architecture writeup.",
    "version": "0.1.0",
    "license": { "name": "MIT" }
  },
  "servers": [{ "url": "/", "description": "This server" }],
  "tags": [
    { "name": "tiles", "description": "/tiles, /stream, /convert — the materialization continuum: live range-streaming through to a fully preprocessed+cached tileset, over one query-param axis (?cache=)." },
    { "name": "transforms", "description": "/3dtiles-self-contained, /3dtiles-tools, /gltf — package extraction and per-tile 3D Tiles/glTF transforms." },
    { "name": "extract", "description": "/extract/{kind} — bounding-box + geometric-error crop/export of a loaded tileset." },
    { "name": "bing", "description": "/bing — Bing Maps 3D (td1 implicit manifest) → OGC 3D Tiles." },
    { "name": "misc", "description": "/proxy, /progress — CORS proxy and job-progress polling." }
  ],
  "paths": {
    "/tiles/tileset.json": {
      "get": {
        "tags": ["tiles"],
        "summary": "GET /tiles/tileset.json — unified entry, dispatches to /stream or /convert based on ?cache=",
        "description": "One HTTP surface over the whole materialization continuum. `cache=none|hierarchy` dispatches in-process to `/stream` (true range-streaming, per-format adapters); `cache=full` dispatches to `/convert` (preprocess the whole dataset once, then serve static). No redirect — the query string carries straight through.",
        "parameters": [
          { "name": "url", "in": "query", "required": true, "schema": { "type": "string", "format": "uri" }, "description": "Absolute URL of the source dataset (e.g. a `.copc.laz`, Potree `cloud.js`, SOG manifest, etc.)." },
          { "name": "format", "in": "query", "schema": { "type": "string", "enum": ["copc", "potree", "sog", "lcc", "rad", "i3s", "threemx", "geosplats"] }, "description": "Source format. Auto-detected from the URL/extension when omitted for most formats." },
          { "name": "cache", "in": "query", "schema": { "type": "string", "enum": ["none", "hierarchy", "full"], "default": "none" }, "description": "none = fully live, nothing cached. hierarchy = tree cached in RAM, tiles per-request. full = whole dataset converted to disk once, then served static." },
          { "name": "prefetch", "in": "query", "schema": { "type": "integer", "default": 1 }, "description": "Octree formats (COPC/Potree) only — number of extra LOD levels to eagerly resolve bounds for on tileset.json build." },
          { "name": "tiling", "in": "query", "schema": { "type": "string", "enum": ["implicit", "explicit"], "default": "implicit" }, "description": "Octree formats only — 3D Tiles 1.1 implicit tiling vs. a fully-materialized explicit tree." }
        ],
        "responses": {
          "200": { "description": "A 3D Tiles tileset.json", "content": { "application/json": { "schema": { "type": "object" } } } },
          "400": { "description": "Bad request (unknown cache level, missing url, etc.)" }
        }
      }
    },
    "/stream/tileset.json": {
      "get": {
        "tags": ["tiles"],
        "summary": "GET /stream/tileset.json — true online range-streaming (copc/potree/sog/lcc/rad/i3s/threemx/geosplats)",
        "description": "Range-reads the source's own persisted hierarchy (no full decode/convert) to build a tileset.json; individual tiles are converted on demand as the client requests them via `/stream/tile/...` or `/stream/subtrees/...` URIs embedded in the response.",
        "parameters": [
          { "name": "url", "in": "query", "required": true, "schema": { "type": "string", "format": "uri" } },
          { "name": "format", "in": "query", "required": true, "schema": { "type": "string", "enum": ["copc", "potree", "sog", "lcc", "rad", "i3s", "threemx", "geosplats"] } },
          { "name": "cache", "in": "query", "schema": { "type": "string", "enum": ["none", "hierarchy"], "default": "none" } },
          { "name": "prefetch", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "tiling", "in": "query", "schema": { "type": "string", "enum": ["implicit", "explicit"], "default": "implicit" }, "description": "COPC/Potree only." }
        ],
        "responses": { "200": { "description": "A 3D Tiles tileset.json" } }
      }
    },
    "/convert/tileset.json": {
      "get": {
        "tags": ["tiles"],
        "summary": "GET /convert/tileset.json — preprocess + cache the whole dataset once (cache=full), any supported format",
        "description": "Converts the entire source dataset to 3D Tiles on first request and caches the result on disk; subsequent requests are served statically. Works for every format the meta-converter supports, including ones with no streaming adapter.",
        "parameters": [
          { "name": "url", "in": "query", "required": true, "schema": { "type": "string", "format": "uri" } },
          { "name": "format", "in": "query", "schema": { "type": "string" }, "description": "Auto-detected from the URL when omitted." },
          { "name": "centroids", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Splat formats only — centroids-only (points, no covariance) preview instead of full Gaussian splats." },
          { "name": "tiling", "in": "query", "schema": { "type": "string", "enum": ["implicit", "explicit"] } }
        ],
        "responses": {
          "200": { "description": "A 3D Tiles tileset.json (subsequent requests served from disk cache)" }
        }
      }
    },
    "/3dtiles-self-contained/{key}": {
      "get": {
        "tags": ["transforms"],
        "summary": "GET /3dtiles-self-contained/{key} — serve a .3tz / .3dtiles self-contained package",
        "description": "`{key}` is a path within the archive (`tileset.json` for the root, or any tile path the root references) — the FIRST path segment is treated as the archive locator.",
        "parameters": [
          { "name": "key", "in": "path", "required": true, "schema": { "type": "string" }, "example": "tileset.json" },
          { "name": "url", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Path or URL to the .3tz/.3dtiles archive." }
        ],
        "responses": { "200": { "description": "tileset.json or a tile file extracted from the archive" } }
      }
    },
    "/3dtiles-tools/tileset.json": {
      "get": {
        "tags": ["transforms"],
        "summary": "GET /3dtiles-tools/tileset.json — 3D Tiles 1.0 → 1.1 transforms",
        "description": "`tool=implicit-to-explicit` reads binary `.subtree` files (OCTREE/QUADTREE) and builds an explicit children tree. `tool=upgrade` reads an existing explicit 1.0 tileset, bumps its version to 1.1, and rewrites content URIs so each b3dm/pnts tile (including nested tileset.json references) is converted to glTF 2.0 on demand — fixing legacy glTF 1.0 b3dm content that most modern renderers can no longer parse natively.",
        "parameters": [
          { "name": "tool", "in": "query", "required": true, "schema": { "type": "string", "enum": ["implicit-to-explicit", "upgrade"] } },
          { "name": "url", "in": "query", "schema": { "type": "string", "format": "uri" }, "description": "Remote tileset.json URL (mutually exclusive with dir)." },
          { "name": "dir", "in": "query", "schema": { "type": "string" }, "description": "Local filesystem path to the tileset directory (mutually exclusive with url)." },
          { "name": "tilesetFile", "in": "query", "schema": { "type": "string", "default": "tileset.json" }, "description": "upgrade tool only — filename within dir/url to read." }
        ],
        "responses": { "200": { "description": "A 3D Tiles 1.1 tileset.json with rewritten content URIs" } }
      }
    },
    "/gltf/tileset.json": {
      "get": {
        "tags": ["transforms"],
        "summary": "GET /gltf/tileset.json — per-tile glTF-Transform pipeline over an existing tileset",
        "description": "Proxies an existing tileset.json, rewriting content URIs through /gltf/tile.glb so each glTF/GLB tile is decoded (Draco, meshopt, KTX2 → PNG/JPEG) on the fly — useful for renderers that can't decode one of those encodings natively.",
        "parameters": [
          { "name": "url", "in": "query", "required": true, "schema": { "type": "string", "format": "uri" } },
          { "name": "ops", "in": "query", "style": "form", "explode": false, "schema": { "type": "array", "items": { "type": "string", "enum": ["decompress", "draco", "meshopt", "ktx"] }, "default": ["decompress"] }, "description": "Comma-separated combination of ops (`style: form, explode: false` — e.g. `ops=draco,ktx`). `decompress` is shorthand for `draco,meshopt,ktx` together." },
          { "name": "tex", "in": "query", "schema": { "type": "string", "enum": ["jpg", "png"], "default": "jpg" }, "description": "Output format for decoded KTX2 textures." }
        ],
        "responses": { "200": { "description": "A tileset.json with rewritten content URIs" } }
      }
    },
    "/gltf/tile.glb": {
      "get": {
        "tags": ["transforms"],
        "summary": "GET /gltf/tile.glb — decode a single glTF/GLB tile (same ops as /gltf/tileset.json)",
        "description": "The per-tile endpoint /gltf/tileset.json's rewritten content URIs point at — callable directly against any glTF/GLB URL, not just ones reached via a rewritten tileset.",
        "parameters": [
          { "name": "url", "in": "query", "required": true, "schema": { "type": "string", "format": "uri" } },
          { "name": "ops", "in": "query", "style": "form", "explode": false, "schema": { "type": "array", "items": { "type": "string", "enum": ["decompress", "draco", "meshopt", "ktx"] }, "default": ["decompress"] }, "description": "Comma-separated combination of ops (`style: form, explode: false` — e.g. `ops=draco,ktx`). `decompress` is shorthand for `draco,meshopt,ktx` together." },
          { "name": "tex", "in": "query", "schema": { "type": "string", "enum": ["jpg", "png"], "default": "jpg" }, "description": "Output format for decoded KTX2 textures." }
        ],
        "responses": { "200": { "description": "A decoded glTF/GLB" } }
      }
    },
    "/bing/tileset.json": {
      "get": {
        "tags": ["bing"],
        "summary": "GET /bing/tileset.json — Bing Maps 3D → OGC 3D Tiles",
        "description": "Wraps Bing Maps' own td1 implicit-tiling manifest (tf=3dv4 GLB tiles) as a standard OGC 3D Tiles tileset.json.",
        "parameters": [
          { "name": "g", "in": "query", "schema": { "type": "string" }, "description": "Bing generation/session id. Defaults to a known-good value if omitted." },
          { "name": "decompress", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Route tiles through the /gltf decode pipeline (Bing content is often Draco/KTX2-encoded)." }
        ],
        "responses": { "200": { "description": "A 3D Tiles tileset.json" } }
      }
    },
    "/extract/{kind}": {
      "get": {
        "tags": ["extract"],
        "summary": "GET /extract/{kind} — bounding-box + geometric-error crop/export of a loaded tileset",
        "description": "Traverses a tileset.json (any format this server can serve), keeping only tiles that intersect the given lon/lat/height bounding box down to a target geometric error, and re-exports the result as the requested output kind.",
        "parameters": [
          { "name": "kind", "in": "path", "required": true, "schema": { "type": "string", "enum": ["las", "glb", "copc", "tileset", "zip", "splat"] } },
          { "name": "url", "in": "query", "required": true, "schema": { "type": "string", "format": "uri" }, "description": "The tileset.json to crop (any endpoint on this server, or a third-party 3D Tiles source)." },
          { "name": "bbox", "in": "query", "required": true, "schema": { "type": "string" }, "example": "-122.42,37.77,-10,-122.41,37.78,50", "description": "west,south,minHeight,east,north,maxHeight — degrees,degrees,meters." },
          { "name": "maxGE", "in": "query", "schema": { "type": "number", "default": 0 }, "description": "Maximum geometric error to descend to. 0 = finest available." },
          { "name": "maxDepth", "in": "query", "schema": { "type": "integer", "default": 99 } },
          { "name": "matrix", "in": "query", "schema": { "type": "string" }, "description": "Optional 16-number column-major modelMatrix override (matches an on-screen Z-offset/local-frame nudge applied in the viewer)." },
          { "name": "stream", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "las kind only — disk-free two-pass streaming instead of buffering in memory." }
        ],
        "responses": {
          "200": { "description": "The requested output file (LAS/LAZ, GLB, COPC, tileset.json, ZIP, or a splat format)" },
          "400": { "description": "Missing/invalid url, bbox, or kind" }
        }
      }
    },
    "/proxy": {
      "get": {
        "tags": ["misc"],
        "summary": "GET /proxy — generic CORS-open passthrough for public sources",
        "description": "Fetches an arbitrary public URL server-side and re-serves the bytes with `Access-Control-Allow-Origin: *` — for sources that don't send CORS headers themselves (the common failure mode for hotlinked XYZ basemap tiles fetched via WebGL texture upload, unlike an `<img>` tag which tolerates missing CORS headers).",
        "parameters": [
          { "name": "url", "in": "query", "required": true, "schema": { "type": "string", "format": "uri" } }
        ],
        "responses": { "200": { "description": "The proxied response body, byte-for-byte, with CORS headers added" } }
      }
    },
    "/progress": {
      "get": {
        "tags": ["misc"],
        "summary": "GET /progress — poll progress of a server-side cache=hierarchy/full operation",
        "parameters": [
          { "name": "key", "in": "query", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Progress snapshot",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "done": { "type": "integer" }, "total": { "type": "integer" }, "finished": { "type": "boolean" } } } } }
          }
        }
      }
    }
  }
}
