{"openapi":"3.1.0","info":{"title":"zarr-passport-proxy","version":"1.1"},"paths":{"/":{"get":{"summary":"Service discovery","description":"The stores this host serves, each with the URL of its own agent guide, plus the endpoints below. Send Accept: text/html for the branded page, or an agent/crawler User-Agent for the same.","responses":{"200":{"description":"Service discovery","content":{"application/json":{"schema":{}}}}}}},"/openapi.json":{"get":{"summary":"This document","description":"The OpenAPI 3.1 contract for this service. Built from the port's own route table, so it describes what this implementation answers rather than what any framework introspected.","responses":{"200":{"description":"This document","content":{"application/json":{"schema":{}}}}}}},"/docs":{"get":{"summary":"API explorer","description":"Swagger UI over this service's own contract. The page loads the viewer from a CDN and points it at /openapi.json; there is no OAuth flow behind it, unlike the reference's.","responses":{"200":{"description":"API explorer","content":{"text/html; charset=utf-8":{"schema":{}}}}}}},"/version":{"get":{"summary":"Build stamp","description":"Which implementation answered and which build: impl is m9 or py, version is the SERVICE version both share, and build is that repository's own commit.","responses":{"200":{"description":"Build stamp","content":{"application/json":{"schema":{}}}}}}},"/ping":{"get":{"summary":"Liveness","description":"Answers \"pong\" as soon as the server is listening. The deployment switch waits on this and on /version, because this proxy builds its coordinate caches before it accepts.","responses":{"200":{"description":"Liveness","content":{"text/plain; charset=utf-8":{"schema":{}}}}}}},"/llms.txt":{"get":{"summary":"Agent entry point","description":"The llms.txt convention: what this service is, where to start, and one line per store.","responses":{"200":{"description":"Agent entry point","content":{"text/plain; charset=utf-8":{"schema":{}}}}}}},"/variables":{"get":{"summary":"List variables","description":"The catalogue GET /query selects from. Each entry names its store, group, query kind and coordinate names.","parameters":[{"name":"store","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}]},"description":"Filter by zarr store name."},{"name":"domain","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}]},"description":"Filter by ENVRI domain: ocean, atmosphere, ecosystem or land."}],"responses":{"200":{"description":"List variables","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Add a variable","description":"Admin. Requires Authorization: Bearer ADMIN_TOKEN. An invalid body is refused BEFORE the token is checked, which is the order FastAPI validates in.","responses":{"201":{"description":"Add a variable","content":{"application/json":{"schema":{}}}}}}},"/variables/suggest":{"get":{"summary":"Suggest variables","description":"Walk the stores and propose catalogue entries for arrays that are not yet listed, by profile and coordinate name. Read-only unless apply=true.","parameters":[{"name":"apply","in":"query","required":false,"schema":{"type":"boolean"},"description":"Insert the suggestions into the catalogue. Admin: requires Authorization: Bearer ADMIN_TOKEN."}],"responses":{"200":{"description":"Suggest variables","content":{"application/json":{"schema":{}}}}}}},"/variables/{var_id}":{"get":{"summary":"One variable","description":"The catalogue entry with this id.","parameters":[{"name":"var_id","in":"path","required":true,"schema":{"type":"string"},"description":"The catalogued variable id."}],"responses":{"200":{"description":"One variable","content":{"application/json":{"schema":{}}}}}},"put":{"summary":"Replace a variable","description":"Admin. Requires Authorization: Bearer ADMIN_TOKEN.","parameters":[{"name":"var_id","in":"path","required":true,"schema":{"type":"string"},"description":"The catalogued variable id."}],"responses":{"200":{"description":"Replace a variable","content":{"application/json":{"schema":{}}}}}},"delete":{"summary":"Delete a variable","description":"Admin. Requires Authorization: Bearer ADMIN_TOKEN.","parameters":[{"name":"var_id","in":"path","required":true,"schema":{"type":"string"},"description":"The catalogued variable id."}],"responses":{"200":{"description":"Delete a variable","content":{"application/json":{"schema":{}}}}}}},"/query":{"get":{"summary":"Query a variable","description":"Server-side selection over a catalogued variable: bounding box, time window, station or cruise, and QC. Answers NDJSON by default or CSV, one row per observation, with a data passport in the X-DataPassport headers. Rows beyond max_rows are TRUNCATED, and the response says so.","parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string"},"description":"Catalogued variable id, as listed by GET /variables."},{"name":"lat_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}]},"description":"Bounding box south edge, degrees north."},{"name":"lat_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}]},"description":"Bounding box north edge, degrees north."},{"name":"lon_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}]},"description":"Bounding box west edge, degrees east."},{"name":"lon_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}]},"description":"Bounding box east edge, degrees east."},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}]},"description":"ISO start date or datetime, inclusive."},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}]},"description":"ISO end date or datetime, exclusive."},{"name":"height_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}]},"description":"Minimum height or depth, where the variable has one."},{"name":"height_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}]},"description":"Maximum height or depth, where the variable has one."},{"name":"station","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}]},"description":"Comma-separated station ids, CASE-SENSITIVE: trigram plus height for atmosphere (CBW207), mixed-case site codes for FLUXNET (SE-Htm)."},{"name":"cruise","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}]},"description":"Comma-separated cruise expocodes; obs-table variables only."},{"name":"qc_max","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}]},"description":"Drop rows whose QC flag exceeds this."},{"name":"apply_qc","in":"query","required":false,"schema":{"type":"boolean"},"description":"Drop rows failing the store's recommended QC."},{"name":"source_doi_prefix","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}]},"description":"By-network or by-provider filter; obs-table variables only."},{"name":"max_rows","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}]},"description":"Row cap; defaults to QUERY_MAX_ROWS. The selection is truncated, not refused."},{"name":"format","in":"query","required":false,"schema":{"type":"string"},"description":"ndjson (default), csv, arrow or parquet. arrow is an Apache Arrow IPC stream and parquet a PLAIN uncompressed Parquet file, both carrying the data passport in their metadata. Both are served for obs-table variables, reduced or not; a station panel or a grid answers a named 501. The binary twins are not byte-identical to the reference, which compresses: the values, the row count and the passport are."},{"name":"reduce","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}]},"description":"Thin the WHOLE selection server-side, per cruise, so the answer represents every matching cruise instead of a truncated head. track keeps each track shape and its value extrema, stride decimates evenly, grid keeps one observation per cell. Obs-table variables only."},{"name":"pos_tol","in":"query","required":false,"schema":{"type":"number"},"description":"Position tolerance in degrees: the track corridor for reduce=track, and the cell size for reduce=grid. Default 4."},{"name":"val_tol","in":"query","required":false,"schema":{"type":"number"},"description":"Value tolerance in the variable unit, for reduce=track. Default 16."}],"responses":{"200":{"description":"Query a variable","content":{"application/json":{"schema":{}}}}}}},"/summary":{"get":{"summary":"Size a selection","description":"Counts for the same selection GET /query would return, without the rows: ask before you fetch. Not served for station-panel variables.","parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string"},"description":"Catalogued variable id, as listed by GET /variables."},{"name":"lat_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}]},"description":"Bounding box south edge, degrees north."},{"name":"lat_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}]},"description":"Bounding box north edge, degrees north."},{"name":"lon_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}]},"description":"Bounding box west edge, degrees east."},{"name":"lon_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}]},"description":"Bounding box east edge, degrees east."},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}]},"description":"ISO start date or datetime, inclusive."},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}]},"description":"ISO end date or datetime, exclusive."},{"name":"qc_max","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}]},"description":"Drop rows whose QC flag exceeds this."},{"name":"source_doi_prefix","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}]},"description":"By-network or by-provider filter; obs-table variables only."}],"responses":{"200":{"description":"Size a selection","content":{"application/json":{"schema":{}}}}}}},"/{store_name}":{"get":{"summary":"Store, redirected","description":"The store URL without its trailing slash. Answers 307 to /{store_name}/ with any query string carried, which is what xarray.open_zarr and every hand-written store URL actually ask for.","parameters":[{"name":"store_name","in":"path","required":true,"schema":{"type":"string"},"description":"Store directory name, e.g. icos-obspack.zarr."}],"responses":{"307":{"description":"Store, redirected","content":{"application/json":{"schema":{}}}}}}},"/{store_name}/":{"get":{"summary":"Store landing","description":"The store root group, as zarr sees it. Send Accept: text/html for the branded page.","parameters":[{"name":"store_name","in":"path","required":true,"schema":{"type":"string"},"description":"Store directory name, e.g. icos-obspack.zarr."}],"responses":{"200":{"description":"Store landing","content":{"application/json":{"schema":{}}}}}}},"/{store_name}/agent_guide":{"get":{"summary":"Store agent guide","description":"How to open and query THIS store, from its own attributes.","parameters":[{"name":"store_name","in":"path","required":true,"schema":{"type":"string"},"description":"Store directory name."}],"responses":{"200":{"description":"Store agent guide","content":{"application/json":{"schema":{}}}}}}},"/{store_name}/croissant":{"get":{"summary":"Croissant metadata","description":"The store's Croissant sidecar, served as it stands when it parses.","parameters":[{"name":"store_name","in":"path","required":true,"schema":{"type":"string"},"description":"Store directory name."}],"responses":{"200":{"description":"Croissant metadata","content":{"application/json":{"schema":{}}}}}}},"/{store_name}/session/passport":{"get":{"summary":"Data passport","description":"The RO-Crate passport for what this client has read from this store: every chunk, its digest, and the citations owed.","parameters":[{"name":"store_name","in":"path","required":true,"schema":{"type":"string"},"description":"Store directory name."},{"name":"status","in":"query","required":false,"schema":{"type":"boolean"},"description":"Answer the session status instead of minting a passport."}],"responses":{"200":{"description":"Data passport","content":{"application/json":{"schema":{}}}}}}},"/{store_name}/session/close":{"post":{"summary":"Close a session","description":"Mint the passport and forget the session. A session is keyed by client address and store.","parameters":[{"name":"store_name","in":"path","required":true,"schema":{"type":"string"},"description":"Store directory name."}],"responses":{"200":{"description":"Close a session","content":{"application/json":{"schema":{}}}}}}},"/{store_name}/{key}":{"get":{"summary":"A zarr key","description":"Any key in the store: .zgroup, .zattrs, .zarray, .zmetadata, or a chunk. This is what xarray.open_zarr reads, and every read is recorded against the session. Append ?list for the fsspec directory listing.","parameters":[{"name":"store_name","in":"path","required":true,"schema":{"type":"string"},"description":"Store directory name."},{"name":"key","in":"path","required":true,"schema":{"type":"string"},"description":"Key within the store, slashes and all."}],"responses":{"200":{"description":"A zarr key","content":{"application/json":{"schema":{}}}}}}},"/robots.txt":{"get":{"summary":"Crawl policy","description":"Allows crawling and points crawlers at the sitemap.","responses":{"200":{"description":"Crawl policy","content":{"text/plain; charset=utf-8":{"schema":{}}}}}}},"/sitemap.xml":{"get":{"summary":"Sitemap","description":"The discovery surface and every store landing.","responses":{"200":{"description":"Sitemap","content":{"application/xml":{"schema":{}}}}}}},"/favicon.ico":{"get":{"summary":"Favicon","responses":{"200":{"description":"Favicon","content":{"image/x-icon":{"schema":{}}}}}}},"/icos_logo.svg":{"get":{"summary":"Logo","responses":{"200":{"description":"Logo","content":{"image/svg+xml":{"schema":{}}}}}}},"/google2f93a3138681285e.html":{"get":{"summary":"Site verification","responses":{"200":{"description":"Site verification","content":{"text/plain; charset=utf-8":{"schema":{}}}}}}},"/blob/{kb}":{"get":{"summary":"Test blob","description":"A synthetic body of {kb} kibibytes with position-dependent bytes, so a load test can detect truncation and cross-connection interleaving. Not data: this exists for the gate.","parameters":[{"name":"kb","in":"path","required":true,"schema":{"type":"integer"},"description":"Size in kibibytes, 1 to 4096."}],"responses":{"200":{"description":"Test blob","content":{"application/octet-stream":{"schema":{}}}}}}}}}