{
  "openapi": "3.1.0",
  "info": {
    "title": "CycleCalcs Astronomy API",
    "version": "2.0.0",
    "description": "A read-only astronomy API that returns the named answer and the numbers behind it, with the frame, epoch, time scale and refraction model stated on every response. It reports angles, times and names, and attaches no meaning to any of them. Computed in real time with the MIT-licensed Astronomy Engine. This document is generated from the shipped route table (netlify/lib/v2/registry.mjs) by tools/generate-openapi.mjs, so it cannot list a route, a parameter or a requirement the API does not actually enforce. Computed astronomical output is unrestricted: use, store, redistribute and sell it without attribution. The one exception is GeoNames place data served by GET /v2/places and by any response that resolved a `place` parameter, which requires the credit line in meta.attribution; see GET /v2/attribution for the full source register.",
    "license": {
      "name": "Computed output unrestricted; third-party data credited per response",
      "url": "https://www.cyclecalcs.com/api/terms.html"
    },
    "termsOfService": "https://www.cyclecalcs.com/api/terms.html"
  },
  "servers": [
    {
      "url": "https://www.cyclecalcs.com"
    }
  ],
  "externalDocs": {
    "description": "Human-readable endpoint reference",
    "url": "https://www.cyclecalcs.com/api/reference.html"
  },
  "paths": {
    "/v2": {
      "get": {
        "operationId": "getDiscovery",
        "summary": "The discovery document: every endpoint, parameter, convention and limit",
        "description": "The discovery document: every endpoint, parameter, convention and limit. Formats: json. Example: GET /v2",
        "parameters": [
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/conventions": {
      "get": {
        "operationId": "getConventions",
        "summary": "Every convention, threshold, model and limit the API uses, as data",
        "description": "Every convention, threshold, model and limit the API uses, as data. Formats: json. Example: GET /v2/conventions",
        "parameters": [
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/enums": {
      "get": {
        "operationId": "getEnums",
        "summary": "Every enumerated value the API can emit or accept, as data",
        "description": "Every enumerated value the API can emit or accept, as data. Formats: json. Some fields are currently withheld: The meteor_showers set is withheld until the meteor catalogue reduction clears its licence review. Example: GET /v2/enums?set=moon_phase_names",
        "parameters": [
          {
            "name": "set",
            "in": "query",
            "required": false,
            "description": "Which /v2/enums set to return. Mutually exclusive with `endpoint`. Omit both for every set.",
            "schema": {
              "type": "string",
              "enum": [
                "ambiguous",
                "bodies",
                "body_aliases_refused",
                "cache_classes",
                "catalogues",
                "compass_points",
                "constellations",
                "cycle_families",
                "cycle_keys",
                "cycle_kinds",
                "day_anchors",
                "eclipse_kinds",
                "error_codes",
                "event_kinds",
                "formats",
                "frame_origin",
                "frames_parameter",
                "hemisphere",
                "include_tokens",
                "interval_kinds",
                "interval_reasons",
                "libration_favoured_limbs",
                "meteor_showers",
                "moon_phase_names",
                "moon_phase_names_south",
                "non_goals",
                "nonexistent",
                "position_origins",
                "query_modes",
                "refraction",
                "rights",
                "rise_set_event_kinds",
                "rise_set_special",
                "rise_set_statuses",
                "shape",
                "style",
                "sun_position_bases",
                "time_format",
                "timezone_sources",
                "verbosity",
                "visibility_phrases",
                "warning_codes",
                "zodiac_signs"
              ]
            },
            "example": "moon_phase_names"
          },
          {
            "$ref": "#/components/parameters/EndpointParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer. Some fields are withheld; see meta.not_computed.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/attribution": {
      "get": {
        "operationId": "getAttribution",
        "summary": "Every source behind every endpoint, with its licence and what it obliges a caller to do",
        "description": "Every source behind every endpoint, with its licence and what it obliges a caller to do. Formats: json. Example: GET /v2/attribution?endpoint=/v2/moon",
        "parameters": [
          {
            "$ref": "#/components/parameters/EndpointParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/positions": {
      "get": {
        "operationId": "getPositions",
        "summary": "Where every body is, in every frame, with refraction stated",
        "description": "Where every body is, in every frame, with refraction stated. Formats: json, csv. Example: GET /v2/positions?bodies=sun,moon&at=2026-07-28T21:30:00Z&lat=51.4778&lon=-0.0015&elevation_m=11&tz=Europe/London",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-07-28T21:30:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2027-01-01"
          },
          {
            "name": "step",
            "in": "query",
            "required": false,
            "description": "Sampling stride for a range query: a number plus a unit, one of min, m (minute), h (hour), d (day), w (week), mo (month) or y (year), for example 1h, 6h or 1d. Resolves to at least 1 minute and at most 1 year. Needs `start`. Some endpoints enforce a narrower range; see the parameter overrides on /v2/jupiter-moons and /v2/twilight.",
            "schema": {
              "type": "string"
            },
            "example": "1d"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Number of rows to return from a range query, counted forward from `start`. Mutually exclusive with `end`.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000
            },
            "example": 30
          },
          {
            "name": "bodies",
            "in": "query",
            "required": false,
            "description": "A comma-separated list of targets, up to 20: any of sun, moon, mercury, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto, or radec:<ra>,<dec>[,j2000] entries. Mutually exclusive with `body`.",
            "schema": {
              "type": "string"
            },
            "example": "sun,moon"
          },
          {
            "name": "body",
            "in": "query",
            "required": false,
            "description": "A single target: one of sun, moon, mercury, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto, or radec:<ra>,<dec>[,j2000] for a fixed celestial point (right ascension in decimal hours, declination in decimal degrees). Mutually exclusive with `bodies`.",
            "schema": {
              "type": "string"
            },
            "example": "moon"
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "name": "frames",
            "in": "query",
            "required": false,
            "description": "Which coordinate frame families to include: minimal is of-date equatorial and horizontal only; standard adds equatorial J2000, true ecliptic-of-date, horizontal and distance; all adds ecliptic J2000 and galactic.",
            "schema": {
              "type": "string",
              "enum": [
                "minimal",
                "standard",
                "all"
              ],
              "default": "standard"
            },
            "example": "standard"
          },
          {
            "name": "frame",
            "in": "query",
            "required": false,
            "description": "Measured from the centre of the Earth, or the centre of the Sun. Earth itself has no geocentric position and needs frame=heliocentric.",
            "schema": {
              "type": "string",
              "enum": [
                "geocentric",
                "heliocentric"
              ],
              "default": "geocentric"
            },
            "example": "geocentric"
          },
          {
            "name": "refraction",
            "in": "query",
            "required": false,
            "description": "normal applies Saemundsson refraction to apparent altitude; none reports the unrefracted geometric altitude.",
            "schema": {
              "type": "string",
              "enum": [
                "normal",
                "none"
              ],
              "default": "normal"
            },
            "example": "normal"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/rise-set": {
      "get": {
        "operationId": "getRiseSet",
        "summary": "Rise, transit, set and lower transit for any named object, with an explicit status at every latitude",
        "description": "Rise, transit, set and lower transit for any named object, with an explicit status at every latitude. Formats: json, csv. Example: GET /v2/rise-set?body=moon&at=2026-12-21&lat=69.6492&lon=18.9553&tz=Europe/Oslo",
        "parameters": [
          {
            "name": "body",
            "in": "query",
            "required": true,
            "description": "A single target: one of sun, moon, mercury, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto, or radec:<ra>,<dec>[,j2000] for a fixed celestial point (right ascension in decimal hours, declination in decimal degrees). Mutually exclusive with `bodies`.",
            "schema": {
              "type": "string"
            },
            "example": "moon"
          },
          {
            "name": "bodies",
            "in": "query",
            "required": false,
            "description": "A comma-separated list of targets, up to 20: any of sun, moon, mercury, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto, or radec:<ra>,<dec>[,j2000] entries. Mutually exclusive with `body`.",
            "schema": {
              "type": "string"
            },
            "example": "sun,moon"
          },
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-12-21"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2027-01-01"
          },
          {
            "$ref": "#/components/parameters/LatitudeRequiredParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeRequiredParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "$ref": "#/components/parameters/DayAnchorParam"
          },
          {
            "name": "horizon",
            "in": "query",
            "required": false,
            "description": "Overrides the rise/set event altitude, in degrees, replacing the standard -0.8333 deg (or the body's own definition). Disclosed with a warning.",
            "schema": {
              "type": "number",
              "minimum": -20,
              "maximum": 20
            },
            "example": -0.5
          },
          {
            "name": "search_horizon_days",
            "in": "query",
            "required": false,
            "description": "How far past the requested window to keep searching for the next real event, in days, when none falls inside the window (for example, through a polar night).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 400
            },
            "example": 200
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/sun": {
      "get": {
        "operationId": "getSun",
        "summary": "The complete solar day: every named boundary, azimuth, day length and polar status",
        "description": "The complete solar day: every named boundary, azimuth, day length and polar status. Formats: json, csv, txt. Example: GET /v2/sun?lat=51.4778&lon=-0.0015&at=2026-07-27&tz=Europe/London&depressions=17,18",
        "parameters": [
          {
            "$ref": "#/components/parameters/LatitudeRequiredParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeRequiredParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-07-27"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2027-01-01"
          },
          {
            "name": "step",
            "in": "query",
            "required": false,
            "description": "Sampling stride for a range query: a number plus a unit, one of min, m (minute), h (hour), d (day), w (week), mo (month) or y (year), for example 1h, 6h or 1d. Resolves to at least 1 minute and at most 1 year. Needs `start`. Some endpoints enforce a narrower range; see the parameter overrides on /v2/jupiter-moons and /v2/twilight.",
            "schema": {
              "type": "string"
            },
            "example": "1d"
          },
          {
            "$ref": "#/components/parameters/DayAnchorParam"
          },
          {
            "name": "depressions",
            "in": "query",
            "required": false,
            "description": "A comma-separated list of up to 8 custom solar depression angles in degrees, 0 to 30, in addition to the civil/nautical/astronomical bands.",
            "schema": {
              "type": "string"
            },
            "example": "17,18"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "A TXT rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/moon": {
      "get": {
        "operationId": "getMoon",
        "summary": "The Moon in render-ready form: phase, illumination, bright limb, libration",
        "description": "The Moon in render-ready form: phase, illumination, bright limb, libration. Formats: json, csv, txt. Example: GET /v2/moon?at=2026-07-27T21:00:00Z&lat=51.4778&lon=-0.0015&tz=Europe/London",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-07-27T21:00:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2027-01-01"
          },
          {
            "name": "step",
            "in": "query",
            "required": false,
            "description": "Sampling stride for a range query: a number plus a unit, one of min, m (minute), h (hour), d (day), w (week), mo (month) or y (year), for example 1h, 6h or 1d. Resolves to at least 1 minute and at most 1 year. Needs `start`. Some endpoints enforce a narrower range; see the parameter overrides on /v2/jupiter-moons and /v2/twilight.",
            "schema": {
              "type": "string"
            },
            "example": "1d"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Number of rows to return from a range query, counted forward from `start`. Mutually exclusive with `end`.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000
            },
            "example": 30
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "name": "hemisphere",
            "in": "query",
            "required": false,
            "description": "Which hemisphere's convention to use (phase emoji, or season pairing). Defaults to south when lat is negative, north otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "north",
                "south"
              ]
            },
            "example": "north"
          },
          {
            "name": "next_phases",
            "in": "query",
            "required": false,
            "description": "How many upcoming quarter phases to list. Default 4.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 24
            },
            "example": 4
          },
          {
            "name": "style",
            "in": "query",
            "required": false,
            "description": "How the phase is rendered on format=txt: the bare name, the phase emoji, or a one-line sentence.",
            "schema": {
              "type": "string",
              "enum": [
                "name",
                "emoji",
                "sentence"
              ],
              "default": "name"
            },
            "example": "name"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "$ref": "#/components/parameters/DayAnchorParam"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "A TXT rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/time": {
      "get": {
        "operationId": "getTime",
        "summary": "Every time scale, sidereal time, solar time, the equation of time and the Julian dates",
        "description": "Every time scale, sidereal time, solar time, the equation of time and the Julian dates. Formats: json, csv. Example: GET /v2/time?at=2026-07-27T12:00:00Z&lat=51.4778&lon=-0.0015",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-07-27T12:00:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2027-01-01"
          },
          {
            "name": "step",
            "in": "query",
            "required": false,
            "description": "Sampling stride for a range query: a number plus a unit, one of min, m (minute), h (hour), d (day), w (week), mo (month) or y (year), for example 1h, 6h or 1d. Resolves to at least 1 minute and at most 1 year. Needs `start`. Some endpoints enforce a narrower range; see the parameter overrides on /v2/jupiter-moons and /v2/twilight.",
            "schema": {
              "type": "string"
            },
            "example": "1d"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Number of rows to return from a range query, counted forward from `start`. Mutually exclusive with `end`.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000
            },
            "example": 30
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/seasons": {
      "get": {
        "operationId": "getSeasons",
        "summary": "Every equinox and solstice in a window, with the length of each season they bound",
        "description": "Every equinox and solstice in a window, with the length of each season they bound. Formats: json, csv. Example: GET /v2/seasons?start=2026-01-01&end=2027-01-01&tz=Europe/London",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-06-21T21:00:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2027-01-01"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Number of rows to return from a range query, counted forward from `start`. Mutually exclusive with `end`.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000
            },
            "example": 30
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "name": "hemisphere",
            "in": "query",
            "required": false,
            "description": "Which hemisphere's convention to use (phase emoji, or season pairing). Defaults to south when lat is negative, north otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "north",
                "south"
              ]
            },
            "example": "north"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional blocks to include. Omit for the endpoint default set.",
            "schema": {
              "type": "string",
              "enum": [
                "events",
                "seasons",
                "cross_quarters",
                "year_length"
              ]
            },
            "example": "events"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/apsides": {
      "get": {
        "operationId": "getApsides",
        "summary": "Every perigee, apogee, perihelion and aphelion in a window, with its distance",
        "description": "Every perigee, apogee, perihelion and aphelion in a window, with its distance. Formats: json, csv. Example: GET /v2/apsides?start=2026-01-01&end=2026-04-01&bodies=moon,earth",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-06-21T21:00:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-04-01"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Number of rows to return from a range query, counted forward from `start`. Mutually exclusive with `end`.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000
            },
            "example": 30
          },
          {
            "name": "body",
            "in": "query",
            "required": false,
            "description": "A single target: one of sun, moon, mercury, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto, or radec:<ra>,<dec>[,j2000] for a fixed celestial point (right ascension in decimal hours, declination in decimal degrees). Mutually exclusive with `bodies`.",
            "schema": {
              "type": "string"
            },
            "example": "moon"
          },
          {
            "name": "bodies",
            "in": "query",
            "required": false,
            "description": "A comma-separated list of targets, up to 20: any of sun, moon, mercury, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto, or radec:<ra>,<dec>[,j2000] entries. Mutually exclusive with `body`.",
            "schema": {
              "type": "string"
            },
            "example": "moon,earth"
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "description": "Filter to the near extremum (perigee/perihelion), the far one (apogee/aphelion), or both.",
            "schema": {
              "type": "string",
              "enum": [
                "near",
                "far",
                "all"
              ],
              "default": "all"
            },
            "example": "all"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional blocks to include; comma-separate multiple tokens. Omit for the endpoint default set.",
            "schema": {
              "type": "string",
              "enum": [
                "events",
                "extremes",
                "nearest_syzygy",
                "angular_size"
              ]
            },
            "example": "events,extremes"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/moon-nodes": {
      "get": {
        "operationId": "getMoonNodes",
        "summary": "Every crossing of the ecliptic by the Moon, the node longitudes and the eclipse seasons they define",
        "description": "Every crossing of the ecliptic by the Moon, the node longitudes and the eclipse seasons they define. Formats: json, csv. Example: GET /v2/moon-nodes?start=2026-01-01&end=2027-01-01",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-06-21T21:00:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2027-01-01"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Number of rows to return from a range query, counted forward from `start`. Mutually exclusive with `end`.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000
            },
            "example": 30
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "name": "node_model",
            "in": "query",
            "required": false,
            "description": "Which lunar node model to report: the engine's true (osculating) node, the analytic mean node, or both side by side.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "mean",
                "both"
              ],
              "default": "true"
            },
            "example": "true"
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "description": "Filter to ascending crossings, descending crossings, or both.",
            "schema": {
              "type": "string",
              "enum": [
                "ascending",
                "descending",
                "all"
              ],
              "default": "all"
            },
            "example": "all"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional blocks to include. Omit for the endpoint default set.",
            "schema": {
              "type": "string",
              "enum": [
                "crossings",
                "node_longitude",
                "eclipse_seasons",
                "periods",
                "standstill"
              ]
            },
            "example": "crossings"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/eclipses": {
      "get": {
        "operationId": "getEclipses",
        "summary": "Solar and lunar eclipses with global geometry, local circumstances and an explicit answer to whether you can see it",
        "description": "Solar and lunar eclipses with global geometry, local circumstances and an explicit answer to whether you can see it. Formats: json, csv, txt. Some fields are currently withheld: The Saros MEMBER number is withheld: it needs a 223-row table of series-start epochs, which is catalogue data, and the obvious source has not cleared a provenance check. The series number is closed-form and is published. Example: GET /v2/eclipses?at=2026-07-28T21:30:00Z&lat=51.4778&lon=-0.0015&elevation_m=11&count=2",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-07-28T21:30:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2027-01-01"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Number of rows to return from a range query, counted forward from `start`. Mutually exclusive with `end`.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000
            },
            "example": 2
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "Search forward from `at`, backward from `at`, or list every eclipse in [start, end). range is implied by sending `start`.",
            "schema": {
              "type": "string",
              "enum": [
                "next",
                "previous",
                "range"
              ],
              "default": "next"
            },
            "example": "next"
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Restrict to solar eclipses, lunar eclipses, or both.",
            "schema": {
              "type": "string",
              "enum": [
                "solar",
                "lunar",
                "both"
              ],
              "default": "both"
            },
            "example": "both"
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "name": "visible_only",
            "in": "query",
            "required": false,
            "description": "When true, and a location is given, restrict results to eclipses that are actually visible from that location.",
            "schema": {
              "type": "boolean"
            },
            "example": false
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional blocks to include. Omit for the endpoint default set.",
            "schema": {
              "type": "string",
              "enum": [
                "global",
                "local",
                "saros",
                "contacts",
                "greatest",
                "path"
              ]
            },
            "example": "global,local"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer. Some fields are withheld; see meta.not_computed.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "A TXT rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/planet-board": {
      "get": {
        "operationId": "getPlanetBoard",
        "summary": "All eight planets in one request: place, brightness, apparent size, retrograde state, next station and whether it is worth looking tonight",
        "description": "All eight planets in one request: place, brightness, apparent size, retrograde state, next station and whether it is worth looking tonight. Formats: json, csv, txt. Example: GET /v2/planet-board?at=2026-07-28T21:30:00Z&lat=51.4778&lon=-0.0015&elevation_m=11&tz=Europe/London",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-07-28T21:30:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2027-01-01"
          },
          {
            "name": "step",
            "in": "query",
            "required": false,
            "description": "Sampling stride for a range query: a number plus a unit, one of min, m (minute), h (hour), d (day), w (week), mo (month) or y (year), for example 1h, 6h or 1d. Resolves to at least 1 minute and at most 1 year. Needs `start`. Some endpoints enforce a narrower range; see the parameter overrides on /v2/jupiter-moons and /v2/twilight.",
            "schema": {
              "type": "string"
            },
            "example": "1d"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Number of rows to return from a range query, counted forward from `start`. Mutually exclusive with `end`.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000
            },
            "example": 30
          },
          {
            "name": "bodies",
            "in": "query",
            "required": false,
            "description": "A comma-separated list of targets, up to 20: any of sun, moon, mercury, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto, or radec:<ra>,<dec>[,j2000] entries. Mutually exclusive with `body`.",
            "schema": {
              "type": "string"
            },
            "example": "sun,moon"
          },
          {
            "name": "body",
            "in": "query",
            "required": false,
            "description": "A single target: one of sun, moon, mercury, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto, or radec:<ra>,<dec>[,j2000] for a fixed celestial point (right ascension in decimal hours, declination in decimal degrees). Mutually exclusive with `bodies`.",
            "schema": {
              "type": "string"
            },
            "example": "moon"
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "$ref": "#/components/parameters/DayAnchorParam"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "refraction",
            "in": "query",
            "required": false,
            "description": "normal applies Saemundsson refraction to apparent altitude; none reports the unrefracted geometric altitude.",
            "schema": {
              "type": "string",
              "enum": [
                "normal",
                "none"
              ],
              "default": "normal"
            },
            "example": "normal"
          },
          {
            "name": "frames",
            "in": "query",
            "required": false,
            "description": "Which coordinate frame families to include: minimal is of-date equatorial and horizontal only; standard adds equatorial J2000, true ecliptic-of-date, horizontal and distance; all adds ecliptic J2000 and galactic.",
            "schema": {
              "type": "string",
              "enum": [
                "minimal",
                "standard",
                "all"
              ],
              "default": "standard"
            },
            "example": "standard"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "A TXT rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/retrogrades": {
      "get": {
        "operationId": "getRetrogrades",
        "summary": "Every retrograde and direct station of every planet in a window, with the loop length, its arc, and the interval of triple crossing",
        "description": "Every retrograde and direct station of every planet in a window, with the loop length, its arc, and the interval of triple crossing. Formats: json, csv. Example: GET /v2/retrogrades?body=mercury&start=2026-09-01&end=2026-12-31&include=periods,stations,triple_crossing,companion_event",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-06-21T21:00:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-09-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-12-31"
          },
          {
            "name": "body",
            "in": "query",
            "required": false,
            "description": "A single target: one of sun, moon, mercury, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto, or radec:<ra>,<dec>[,j2000] for a fixed celestial point (right ascension in decimal hours, declination in decimal degrees). Mutually exclusive with `bodies`.",
            "schema": {
              "type": "string"
            },
            "example": "mercury"
          },
          {
            "name": "bodies",
            "in": "query",
            "required": false,
            "description": "A comma-separated list of targets, up to 20: any of sun, moon, mercury, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto, or radec:<ra>,<dec>[,j2000] entries. Mutually exclusive with `body`.",
            "schema": {
              "type": "string"
            },
            "example": "sun,moon"
          },
          {
            "name": "motion",
            "in": "query",
            "required": false,
            "description": "Filter stations by the motion they begin.",
            "schema": {
              "type": "string",
              "enum": [
                "retrograde",
                "direct",
                "all"
              ],
              "default": "all"
            },
            "example": "all"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional blocks to include. Omit for the endpoint default set.",
            "schema": {
              "type": "string",
              "enum": [
                "periods",
                "stations",
                "triple_crossing",
                "companion_event",
                "rate_curve"
              ]
            },
            "example": "periods,stations,triple_crossing,companion_event"
          },
          {
            "name": "step",
            "in": "query",
            "required": false,
            "description": "Sampling stride for a range query: a number plus a unit, one of min, m (minute), h (hour), d (day), w (week), mo (month) or y (year), for example 1h, 6h or 1d. Resolves to at least 1 minute and at most 1 year. Needs `start`. Some endpoints enforce a narrower range; see the parameter overrides on /v2/jupiter-moons and /v2/twilight.",
            "schema": {
              "type": "string"
            },
            "example": "1d"
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/conjunctions": {
      "get": {
        "operationId": "getConjunctions",
        "summary": "Close approaches between body pairs in a window, ranked, with the separation and whether the pair is observable or lost in twilight",
        "description": "Close approaches between body pairs in a window, ranked, with the separation and whether the pair is observable or lost in twilight. Formats: json, csv. Example: GET /v2/conjunctions?bodies=venus,jupiter,mars&start=2026-01-01&end=2027-01-01&max_separation_deg=20",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-06-21T21:00:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2027-01-01"
          },
          {
            "name": "bodies",
            "in": "query",
            "required": false,
            "description": "A comma-separated list of targets, up to 20: any of sun, moon, mercury, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto, or radec:<ra>,<dec>[,j2000] entries. Mutually exclusive with `body`.",
            "schema": {
              "type": "string"
            },
            "example": "venus,jupiter,mars"
          },
          {
            "name": "pairs",
            "in": "query",
            "required": false,
            "description": "An explicit list of body pairs to check, each written as two body names joined by a hyphen, separated by commas, for example jupiter-saturn,venus-mars. Overrides the all-pairs sweep of `bodies`.",
            "schema": {
              "type": "string"
            },
            "example": "jupiter-saturn"
          },
          {
            "name": "max_separation_deg",
            "in": "query",
            "required": false,
            "description": "Only report a minimum whose separation falls at or below this many degrees. Filters the result; never changes the search.",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 180
            },
            "example": 20
          },
          {
            "name": "min_elongation_deg",
            "in": "query",
            "required": false,
            "description": "Only report a pair whose solar elongation is at or above this many degrees, so a conjunction lost in the Sun's glare can be excluded.",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 180
            },
            "example": 0
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "description": "Filter by how the approach is classified.",
            "schema": {
              "type": "string",
              "enum": [
                "conjunction",
                "close_approach",
                "all"
              ],
              "default": "all"
            },
            "example": "all"
          },
          {
            "name": "frame",
            "in": "query",
            "required": false,
            "description": "The frame the separation is measured in. ecliptic compares ecliptic longitude only, the almanac conjunction definition, and is unsupported for a radec: target.",
            "schema": {
              "type": "string",
              "enum": [
                "apparent",
                "geocentric",
                "ecliptic"
              ],
              "default": "apparent"
            },
            "example": "apparent"
          },
          {
            "name": "step",
            "in": "query",
            "required": false,
            "description": "Sampling stride for a range query: a number plus a unit, one of min, m (minute), h (hour), d (day), w (week), mo (month) or y (year), for example 1h, 6h or 1d. Resolves to at least 1 minute and at most 1 year. Needs `start`. Some endpoints enforce a narrower range; see the parameter overrides on /v2/jupiter-moons and /v2/twilight.",
            "schema": {
              "type": "string"
            },
            "example": "1d"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort order of the returned approaches.",
            "schema": {
              "type": "string",
              "enum": [
                "time",
                "separation"
              ],
              "default": "time"
            },
            "example": "time"
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/separation": {
      "get": {
        "operationId": "getSeparation",
        "summary": "The angular separation between any two targets at an instant, and the minima of that separation across a window",
        "description": "The angular separation between any two targets at an instant, and the minima of that separation across a window. Formats: json, csv, txt. Example: GET /v2/separation?a=jupiter&b=saturn&start=2020-01-01&end=2021-01-01&max_separation_deg=1",
        "parameters": [
          {
            "name": "a",
            "in": "query",
            "required": true,
            "description": "The first target: a body name (see `bodies` above) or a radec: target. Required.",
            "schema": {
              "type": "string"
            },
            "example": "jupiter"
          },
          {
            "name": "b",
            "in": "query",
            "required": true,
            "description": "The second target, in the same grammar as `a`, and different from it. Required.",
            "schema": {
              "type": "string"
            },
            "example": "saturn"
          },
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-06-21T21:00:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2020-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2021-01-01"
          },
          {
            "name": "step",
            "in": "query",
            "required": false,
            "description": "Sampling stride for a range query: a number plus a unit, one of min, m (minute), h (hour), d (day), w (week), mo (month) or y (year), for example 1h, 6h or 1d. Resolves to at least 1 minute and at most 1 year. Needs `start`. Some endpoints enforce a narrower range; see the parameter overrides on /v2/jupiter-moons and /v2/twilight.",
            "schema": {
              "type": "string"
            },
            "example": "1d"
          },
          {
            "name": "max_separation_deg",
            "in": "query",
            "required": false,
            "description": "Only report a minimum whose separation falls at or below this many degrees. Filters the result; never changes the search.",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 180
            },
            "example": 1
          },
          {
            "name": "frame",
            "in": "query",
            "required": false,
            "description": "The frame the separation is measured in. ecliptic compares ecliptic longitude only and is unsupported for a radec: target.",
            "schema": {
              "type": "string",
              "enum": [
                "apparent",
                "geocentric",
                "ecliptic"
              ],
              "default": "apparent"
            },
            "example": "apparent"
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "A TXT rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/cycles": {
      "get": {
        "operationId": "getCycles",
        "summary": "Where we are in every astronomical cycle the site tracks, with an explicit statement of whether each number was computed, observed, or is an interval with no live phase",
        "description": "Where we are in every astronomical cycle the site tracks, with an explicit statement of whether each number was computed, observed, or is an interval with no live phase. Formats: json, csv, txt. Example: GET /v2/cycles?at=2026-07-28T21:30:00Z",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-07-28T21:30:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2027-01-01"
          },
          {
            "name": "step",
            "in": "query",
            "required": false,
            "description": "Sampling stride for a range query: a number plus a unit, one of min, m (minute), h (hour), d (day), w (week), mo (month) or y (year), for example 1h, 6h or 1d. Resolves to at least 1 minute and at most 1 year. Needs `start`. Some endpoints enforce a narrower range; see the parameter overrides on /v2/jupiter-moons and /v2/twilight.",
            "schema": {
              "type": "string"
            },
            "example": "1d"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Number of rows to return from a range query, counted forward from `start`. Mutually exclusive with `end`.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000
            },
            "example": 30
          },
          {
            "name": "keys",
            "in": "query",
            "required": false,
            "description": "A comma-separated list of specific cycle keys to return. Mutually exclusive with `kind`/`family`. Omit for every cycle.",
            "schema": {
              "type": "string",
              "enum": [
                "anomalistic_month",
                "anomalistic_year",
                "axial_precession",
                "callippic",
                "draconic_month",
                "eclipse_year",
                "exeligmos",
                "great_conjunction",
                "hale",
                "inex",
                "lunar_nodal_cycle",
                "lunar_standstill",
                "metonic_cycle",
                "saros",
                "sidereal_month",
                "sidereal_year",
                "sunspot_cycle",
                "synodic_month",
                "tritos",
                "tropical_year",
                "venus_pentagram"
              ]
            },
            "example": "synodic_month"
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "description": "Filter cycles by how their current position is known: computed from the ephemeris, observed (a live dataset), a named interval with no live phase, or a discrete state.",
            "schema": {
              "type": "string",
              "enum": [
                "computed_phase",
                "observed",
                "interval",
                "state"
              ]
            },
            "example": "computed_phase"
          },
          {
            "name": "family",
            "in": "query",
            "required": false,
            "description": "Filter to one cycle family. Mutually exclusive with `keys`.",
            "schema": {
              "type": "string",
              "enum": [
                "conjunction",
                "eclipse",
                "eclipse_period",
                "lunar_month",
                "node",
                "secular",
                "sunspot",
                "venus",
                "year"
              ]
            },
            "example": "conjunction"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional blocks to include. Omit for the endpoint default set.",
            "schema": {
              "type": "string",
              "enum": [
                "fraction",
                "next_event",
                "observed",
                "definition"
              ]
            },
            "example": "fraction,next_event"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "A TXT rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/twilight": {
      "get": {
        "operationId": "getTwilight",
        "summary": "All three twilights plus day and night as non-overlapping intervals that tile the whole day, each with the reason it starts and ends, and a checksum the caller can assert",
        "description": "All three twilights plus day and night as non-overlapping intervals that tile the whole day, each with the reason it starts and ends, and a checksum the caller can assert. Formats: json, csv. Example: GET /v2/twilight?lat=51.509&lon=-0.126&at=2026-11-14&tz=Europe/London",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-11-14"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2027-01-01"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Number of rows to return from a range query, counted forward from `start`. Mutually exclusive with `end`.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000
            },
            "example": 30
          },
          {
            "name": "step",
            "in": "query",
            "required": false,
            "description": "Fixed at 1d: this endpoint answers a per-day question and accepts no other stride.",
            "schema": {
              "type": "string",
              "enum": [
                "1d"
              ]
            },
            "example": "1d"
          },
          {
            "$ref": "#/components/parameters/LatitudeRequiredParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeRequiredParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "$ref": "#/components/parameters/DayAnchorParam"
          },
          {
            "name": "depressions",
            "in": "query",
            "required": false,
            "description": "A comma-separated list of up to 8 custom solar depression angles in degrees, 0 to 30, in addition to the civil/nautical/astronomical bands.",
            "schema": {
              "type": "string"
            },
            "example": "17,18"
          },
          {
            "name": "refraction",
            "in": "query",
            "required": false,
            "description": "normal applies Saemundsson refraction to apparent altitude; none reports the unrefracted geometric altitude.",
            "schema": {
              "type": "string",
              "enum": [
                "normal",
                "none"
              ],
              "default": "normal"
            },
            "example": "normal"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/dark-window": {
      "get": {
        "operationId": "getDarkWindow",
        "summary": "The genuinely dark moonless observing window of a night: astronomical night intersected with the Moon being down, ranked across up to 62 nights with a trend",
        "description": "The genuinely dark moonless observing window of a night: astronomical night intersected with the Moon being down, ranked across up to 62 nights with a trend. Formats: json, csv. Example: GET /v2/dark-window?lat=51.509&lon=-0.126&at=2026-11-14&tz=Europe/London&nights=3",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-11-14"
          },
          {
            "$ref": "#/components/parameters/LatitudeRequiredParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeRequiredParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "name": "nights",
            "in": "query",
            "required": false,
            "description": "How many consecutive local nights to rank, starting the night `at` falls in.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 62
            },
            "example": 3
          },
          {
            "name": "sun_depression_deg",
            "in": "query",
            "required": false,
            "description": "The solar depression angle, in degrees, that defines \"dark\" for this request. Default 18 (astronomical night).",
            "schema": {
              "type": "number",
              "minimum": 6,
              "maximum": 30
            },
            "example": 18
          },
          {
            "name": "moon_altitude_max_deg",
            "in": "query",
            "required": false,
            "description": "The Moon must be at or below this altitude, in degrees, to count as \"down\". Default 0.",
            "schema": {
              "type": "number",
              "minimum": -90,
              "maximum": 90
            },
            "example": 0
          },
          {
            "name": "moon_illumination_max",
            "in": "query",
            "required": false,
            "description": "Treat the Moon as \"down\" whenever its illuminated fraction is at or below this value, whatever its altitude. Default 1 (illumination never overrides altitude).",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 1
            },
            "example": 0.15
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/sidereal-time": {
      "get": {
        "operationId": "getSiderealTime",
        "summary": "Greenwich and local sidereal time, apparent and mean side by side, with the equation of the equinoxes that separates them and the nutation terms it is built from",
        "description": "Greenwich and local sidereal time, apparent and mean side by side, with the equation of the equinoxes that separates them and the nutation terms it is built from. Formats: json, csv, txt. Example: GET /v2/sidereal-time?lon=-0.126&lat=51.509&at=2026-11-14T21:30:00Z",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-11-14T21:30:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2027-01-01"
          },
          {
            "name": "step",
            "in": "query",
            "required": false,
            "description": "Sampling stride for a range query: a number plus a unit, one of min, m (minute), h (hour), d (day), w (week), mo (month) or y (year), for example 1h, 6h or 1d. Resolves to at least 1 minute and at most 1 year. Needs `start`. Some endpoints enforce a narrower range; see the parameter overrides on /v2/jupiter-moons and /v2/twilight.",
            "schema": {
              "type": "string"
            },
            "example": "1d"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Number of rows to return from a range query, counted forward from `start`. Mutually exclusive with `end`.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000
            },
            "example": 30
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "A TXT rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/equation-of-time": {
      "get": {
        "operationId": "getEquationOfTime",
        "summary": "How far a sundial runs ahead of or behind the clock, from the real Sun rather than a day-of-year series, with the Sun place that makes a range query an analemma",
        "description": "How far a sundial runs ahead of or behind the clock, from the real Sun rather than a day-of-year series, with the Sun place that makes a range query an analemma. Formats: json, csv, txt. Example: GET /v2/equation-of-time?lon=-0.126&at=2026-11-14T21:30:00Z",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-11-14T21:30:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2027-01-01"
          },
          {
            "name": "step",
            "in": "query",
            "required": false,
            "description": "Sampling stride for a range query: a number plus a unit, one of min, m (minute), h (hour), d (day), w (week), mo (month) or y (year), for example 1h, 6h or 1d. Resolves to at least 1 minute and at most 1 year. Needs `start`. Some endpoints enforce a narrower range; see the parameter overrides on /v2/jupiter-moons and /v2/twilight.",
            "schema": {
              "type": "string"
            },
            "example": "1d"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Number of rows to return from a range query, counted forward from `start`. Mutually exclusive with `end`.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000
            },
            "example": 30
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional blocks to include. Omit for the endpoint default set.",
            "schema": {
              "type": "string",
              "enum": [
                "basic",
                "extremes"
              ]
            },
            "example": "basic"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "A TXT rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/libration": {
      "get": {
        "operationId": "getLibration",
        "summary": "Lunar libration, the sub-Earth point, the axis and bright-limb position angles, and with the terminator token the sub-solar point and the colongitude that fixes where the terminator falls",
        "description": "Lunar libration, the sub-Earth point, the axis and bright-limb position angles, and with the terminator token the sub-solar point and the colongitude that fixes where the terminator falls. Formats: json, csv. Example: GET /v2/libration?at=2026-11-14T21:30:00Z&include=basic,terminator",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-11-14T21:30:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2027-01-01"
          },
          {
            "name": "step",
            "in": "query",
            "required": false,
            "description": "Sampling stride for a range query: a number plus a unit, one of min, m (minute), h (hour), d (day), w (week), mo (month) or y (year), for example 1h, 6h or 1d. Resolves to at least 1 minute and at most 1 year. Needs `start`. Some endpoints enforce a narrower range; see the parameter overrides on /v2/jupiter-moons and /v2/twilight.",
            "schema": {
              "type": "string"
            },
            "example": "1d"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Number of rows to return from a range query, counted forward from `start`. Mutually exclusive with `end`.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000
            },
            "example": 30
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional blocks to include; terminator adds the sub-solar point and colongitude. Omit for the endpoint default set.",
            "schema": {
              "type": "string",
              "enum": [
                "basic",
                "terminator"
              ]
            },
            "example": "basic,terminator"
          },
          {
            "name": "refraction",
            "in": "query",
            "required": false,
            "description": "normal applies Saemundsson refraction to apparent altitude; none reports the unrefracted geometric altitude.",
            "schema": {
              "type": "string",
              "enum": [
                "normal",
                "none"
              ],
              "default": "normal"
            },
            "example": "normal"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/jupiter-moons": {
      "get": {
        "operationId": "getJupiterMoons",
        "summary": "Where the four Galilean moons are, and every transit, shadow transit, eclipse and occultation in the window, with both contact instants for each",
        "description": "Where the four Galilean moons are, and every transit, shadow transit, eclipse and occultation in the window, with both contact instants for each. Formats: json, csv. Example: GET /v2/jupiter-moons?at=2026-11-15T09:40:00Z&step=1m",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-11-15T09:40:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-01-01"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2027-01-01"
          },
          {
            "name": "step",
            "in": "query",
            "required": false,
            "description": "Sampling stride, 10 to 600 seconds (narrower than the generic step range: a Galilean contact needs finer control). A number plus s/sec or m/min.",
            "schema": {
              "type": "string",
              "enum": [
                "10s",
                "30s",
                "1m",
                "5m",
                "10m"
              ]
            },
            "example": "1m"
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "name": "moons",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of Galilean moons to include. Omit for all four.",
            "schema": {
              "type": "string",
              "enum": [
                "io",
                "europa",
                "ganymede",
                "callisto"
              ]
            },
            "example": "io,europa,ganymede,callisto"
          },
          {
            "name": "events",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of event families to include. Omit for all four.",
            "schema": {
              "type": "string",
              "enum": [
                "transit",
                "shadow",
                "eclipse",
                "occultation"
              ]
            },
            "example": "transit,shadow,eclipse,occultation"
          },
          {
            "name": "refraction",
            "in": "query",
            "required": false,
            "description": "normal applies Saemundsson refraction to apparent altitude; none reports the unrefracted geometric altitude.",
            "schema": {
              "type": "string",
              "enum": [
                "normal",
                "none"
              ],
              "default": "normal"
            },
            "example": "normal"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/sky-quality": {
      "get": {
        "operationId": "getSkyQuality",
        "summary": "Naked-eye limiting magnitude and aperture reach for a sky class you supply, with an explicit machine-readable statement that the number is a tuned estimate rather than a measurement",
        "description": "Naked-eye limiting magnitude and aperture reach for a sky class you supply, with an explicit machine-readable statement that the number is a tuned estimate rather than a measurement. Formats: json, csv, txt. Example: GET /v2/sky-quality?bortle=3&equipment=telescope&aperture_mm=200",
        "parameters": [
          {
            "name": "bortle",
            "in": "query",
            "required": false,
            "description": "The Bortle dark-sky class, 1 (excellent) to 9 (inner-city). Mutually exclusive with `nelm`; one of the two is required.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 9
            },
            "example": 3
          },
          {
            "name": "nelm",
            "in": "query",
            "required": false,
            "description": "Naked-eye limiting magnitude, 2.0 to 8.5. Mutually exclusive with `bortle`; one of the two is required.",
            "schema": {
              "type": "number",
              "minimum": 2,
              "maximum": 8.5
            },
            "example": 6
          },
          {
            "name": "equipment",
            "in": "query",
            "required": false,
            "description": "What the observation is made with.",
            "schema": {
              "type": "string",
              "enum": [
                "naked_eye",
                "binoculars",
                "telescope"
              ],
              "default": "naked_eye"
            },
            "example": "telescope"
          },
          {
            "name": "aperture_mm",
            "in": "query",
            "required": false,
            "description": "Telescope aperture in millimetres. Only used when equipment=telescope.",
            "schema": {
              "type": "number",
              "minimum": 5,
              "maximum": 5000
            },
            "example": 200
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-06-21T21:00:00Z"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional blocks to include. Omit for the endpoint default set.",
            "schema": {
              "type": "string",
              "enum": [
                "showpieces",
                "ladder",
                "sky_conditions"
              ]
            },
            "example": "ladder"
          },
          {
            "name": "refraction",
            "in": "query",
            "required": false,
            "description": "normal applies Saemundsson refraction to apparent altitude; none reports the unrefracted geometric altitude.",
            "schema": {
              "type": "string",
              "enum": [
                "normal",
                "none"
              ],
              "default": "normal"
            },
            "example": "normal"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "A TXT rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/places": {
      "get": {
        "operationId": "getPlaces",
        "summary": "Place-name search and reverse lookup against the self-hosted GeoNames index, returning name, region, country, coordinates and IANA timezone with a stable place_id",
        "description": "Place-name search and reverse lookup against the self-hosted GeoNames index, returning name, region, country, coordinates and IANA timezone with a stable place_id. Formats: json, csv. Every response is GeoNames data under CC BY 4.0, so every response carries rights: attribution_required and the credit in meta.attribution.text. The rest of the API is unrestricted. Example: GET /v2/places?q=cambridge&limit=5",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "A place name to search for, at least 2 characters after trimming. Mutually exclusive with `id` and with `lat`/`lon`.",
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 128
            },
            "example": "cambridge"
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "An exact place_id from a previous /v2/places response, for example us-n35198-w111651. Mutually exclusive with `q` and with `lat`/`lon`.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z]{2}-[NSns]\\d{5}-[EWew]\\d{6}$"
            },
            "example": "gb-n52125-w000008"
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "Restricts a name search to one country: an ISO 3166-1 alpha-2 code (case-insensitive) or a country name.",
            "schema": {
              "type": "string"
            },
            "example": "GB"
          },
          {
            "name": "region",
            "in": "query",
            "required": false,
            "description": "Restricts a name search to one admin region (state or province name) as stored in the index.",
            "schema": {
              "type": "string"
            },
            "example": "Cambridgeshire"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of place matches to return, 1 to 25.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 25,
              "default": 6
            },
            "example": 5
          },
          {
            "name": "min_population",
            "in": "query",
            "required": false,
            "description": "Only return places at or above this population. The index stores population to the nearest 1,000, so the value is rounded up to the next thousand.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 40000000
            },
            "example": 100000
          },
          {
            "name": "radius_km",
            "in": "query",
            "required": false,
            "description": "Search radius in kilometres for a reverse (lat/lon) lookup. Default 250.",
            "schema": {
              "type": "number",
              "minimum": 1,
              "maximum": 2000
            },
            "example": 25
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer. Every response is GeoNames data under CC BY 4.0, so every response carries rights: attribution_required and the credit in meta.attribution.text. The rest of the API is unrestricted.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/phases": {
      "get": {
        "operationId": "getPhases",
        "summary": "The lunar phase calendar: every quarter with its distance, apparent size, supermoon classification under BOTH competing rules, traditional name and any eclipse",
        "description": "The lunar phase calendar: every quarter with its distance, apparent size, supermoon classification under BOTH competing rules, traditional name and any eclipse. Formats: json, csv, txt. Example: GET /v2/phases?start=2026-07-29&end=2026-08-31&tz=Europe/London",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-06-21T21:00:00Z"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "The start of a range query, inclusive. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-07-29"
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "The end of a range query (rows on or before it are included). ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-08-31"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Number of rows to return from a range query, counted forward from `start`. Mutually exclusive with `end`.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000
            },
            "example": 30
          },
          {
            "name": "phases",
            "in": "query",
            "required": false,
            "description": "A comma-separated list of quarter names to restrict the calendar to. Omit for all four.",
            "schema": {
              "type": "string",
              "enum": [
                "new_moon",
                "first_quarter",
                "full_moon",
                "last_quarter"
              ]
            },
            "example": "full_moon"
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "$ref": "#/components/parameters/DayAnchorParam"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated optional blocks to include. Omit for the endpoint default set.",
            "schema": {
              "type": "string",
              "enum": [
                "distance",
                "classification",
                "names",
                "eclipse",
                "libration",
                "rise_set"
              ]
            },
            "example": "distance,classification"
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A CSV rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "A TXT rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v2/today": {
      "get": {
        "operationId": "getToday",
        "summary": "The whole sky for a place right now, in one request",
        "description": "The whole sky for a place right now, in one request. Formats: json, txt. Some fields are currently withheld: Meteor-shower events are withheld from next_events until the meteor catalogue reduction clears its licence review. Example: GET /v2/today?at=2026-07-28T21:00:00Z&lat=51.4778&lon=-0.0015&tz=Europe/London",
        "parameters": [
          {
            "name": "at",
            "in": "query",
            "required": false,
            "description": "The instant to evaluate. ISO 8601: a date (2026-06-21), or a date and time with an optional seconds, fraction and either Z or a numeric UTC offset. No offset and no `tz` reads as UTC. Reliable from 1700 to 2200.",
            "schema": {
              "type": "string"
            },
            "example": "2026-07-28T21:00:00Z"
          },
          {
            "$ref": "#/components/parameters/LatitudeParam"
          },
          {
            "$ref": "#/components/parameters/LongitudeParam"
          },
          {
            "$ref": "#/components/parameters/ElevationMParam"
          },
          {
            "$ref": "#/components/parameters/PlaceParam"
          },
          {
            "$ref": "#/components/parameters/PlaceStrategyParam"
          },
          {
            "$ref": "#/components/parameters/TzParam"
          },
          {
            "$ref": "#/components/parameters/AmbiguousParam"
          },
          {
            "$ref": "#/components/parameters/NonexistentParam"
          },
          {
            "$ref": "#/components/parameters/DayAnchorParam"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "See the endpoint description at /api/reference.html.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/TimeFormatParam"
          },
          {
            "$ref": "#/components/parameters/FormatParam"
          },
          {
            "$ref": "#/components/parameters/FieldsParam"
          },
          {
            "$ref": "#/components/parameters/ShapeParam"
          },
          {
            "$ref": "#/components/parameters/PrecisionParam"
          },
          {
            "$ref": "#/components/parameters/PrettyParam"
          },
          {
            "$ref": "#/components/parameters/VerbosityParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer. Some fields are withheld; see meta.not_computed.",
            "headers": {
              "ETag": {
                "description": "Present when the cache class is deterministic or time-quantised. Send back as If-None-Match to get 304.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Browser caching directive for this response's cache class.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "draft-ietf-httpapi-ratelimit-headers usage for the current window.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "A TXT rendering of the same answer. See meta and the CSV header comment for provenance."
                }
              }
            }
          },
          "304": {
            "description": "Not Modified. Returned when If-None-Match matches the current ETag. No body."
          },
          "400": {
            "description": "The request could not be understood: a malformed or out-of-range parameter, an incomplete range, a conflicting pair of parameters, or a request too large for the compute budget.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not found: a `place`/`id`/target that does not resolve to anything, or a path this API does not serve.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. This API serves GET, HEAD and OPTIONS only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The serialised response exceeded the hard size cap. Narrow the request with `fields`, verbosity=compact or a smaller range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait the number of seconds in Retry-After, or cache the response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "FormatParam": {
        "name": "format",
        "in": "query",
        "required": false,
        "description": "Response representation. Each endpoint serves a subset of json, csv and txt; see that operation's description for the exact list, or GET /v2 for every endpoint's formats array. An unsupported value is 400 BAD_FORMAT with the accepted list in `supported`.",
        "schema": {
          "type": "string"
        },
        "example": "json"
      },
      "PrecisionParam": {
        "name": "precision",
        "in": "query",
        "required": false,
        "description": "Decimal places for numeric fields.",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9,
          "default": 6
        },
        "example": 6
      },
      "PrettyParam": {
        "name": "pretty",
        "in": "query",
        "required": false,
        "description": "Whether the JSON body is indented (1) or compact (0). Has no effect on csv or txt.",
        "schema": {
          "type": "integer",
          "enum": [
            0,
            1
          ],
          "default": 1
        },
        "example": 1
      },
      "VerbosityParam": {
        "name": "verbosity",
        "in": "query",
        "required": false,
        "description": "full includes prose notes, frame definitions and withheld-field reasons in meta. compact keeps every value and every machine-readable name and drops the prose.",
        "schema": {
          "type": "string",
          "enum": [
            "full",
            "compact"
          ],
          "default": "full"
        },
        "example": "full"
      },
      "ShapeParam": {
        "name": "shape",
        "in": "query",
        "required": false,
        "description": "nested returns the documented object tree. flat returns one object with dotted keys, format=json only.",
        "schema": {
          "type": "string",
          "enum": [
            "nested",
            "flat"
          ],
          "default": "nested"
        },
        "example": "nested"
      },
      "TimeFormatParam": {
        "name": "time_format",
        "in": "query",
        "required": false,
        "description": "How instants are rendered: iso for ISO 8601 strings carrying the effective offset, or unix for integer seconds since the epoch.",
        "schema": {
          "type": "string",
          "enum": [
            "iso",
            "unix"
          ],
          "default": "iso"
        },
        "example": "iso"
      },
      "FieldsParam": {
        "name": "fields",
        "in": "query",
        "required": false,
        "description": "Comma-separated dot paths into `data` (for example moon.phase_name,moon.illumination) so only those fields are returned. Up to 64 paths; an array element is addressed by its object key, never by index.",
        "schema": {
          "type": "string"
        }
      },
      "LimitParam": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Maximum rows to return from a range query, for cursor pagination. The per-endpoint cap is published at /v2/conventions; omit it to take everything the response size cap allows. /v2/places uses `limit` differently: see that operation.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "CursorParam": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "An opaque, unsigned pagination token copied verbatim from a previous response's links.next. Never construct one by hand; a malformed or foreign cursor is 400 BAD_CURSOR.",
        "schema": {
          "type": "string"
        }
      },
      "ElevationMParam": {
        "name": "elevation_m",
        "in": "query",
        "required": false,
        "description": "Observer elevation above mean sea level, in metres. Raises the observer on a locally flat plateau; sea-horizon dip is not modelled.",
        "schema": {
          "type": "number",
          "minimum": -500,
          "maximum": 9000,
          "default": 0
        },
        "example": 11
      },
      "TzParam": {
        "name": "tz",
        "in": "query",
        "required": false,
        "description": "An IANA time zone name, for example Europe/London or America/Chicago. Numeric offsets and abbreviations are not accepted. Governs how a date-only or offset-free `at`/`start`/`end` is read, and how local instants are rendered when time_format=iso.",
        "schema": {
          "type": "string"
        },
        "example": "Europe/London"
      },
      "AmbiguousParam": {
        "name": "ambiguous",
        "in": "query",
        "required": false,
        "description": "How to resolve a local time that a clock repeated, such as the return from daylight saving. error refuses with 409 AMBIGUOUS_LOCAL_TIME.",
        "schema": {
          "type": "string",
          "enum": [
            "first",
            "last",
            "error"
          ],
          "default": "first"
        },
        "example": "first"
      },
      "NonexistentParam": {
        "name": "nonexistent",
        "in": "query",
        "required": false,
        "description": "How to resolve a local time that never happened, such as inside a spring-forward gap. error refuses with 400 NONEXISTENT_LOCAL_TIME.",
        "schema": {
          "type": "string",
          "enum": [
            "error",
            "shift_forward",
            "shift_back"
          ],
          "default": "error"
        },
        "example": "error"
      },
      "DayAnchorParam": {
        "name": "day_anchor",
        "in": "query",
        "required": false,
        "description": "civil: Local midnight in the effective IANA zone, with the historical offset for that date. solar: Local mean solar midnight implied by longitude alone. No timezone, no DST. utc: UTC midnight of the resolved UTC calendar date.",
        "schema": {
          "type": "string",
          "enum": [
            "civil",
            "solar",
            "utc"
          ]
        },
        "example": "civil"
      },
      "PlaceParam": {
        "name": "place",
        "in": "query",
        "required": false,
        "description": "A place name (place=London), a name with a country (place=Cambridge,GB), or a stable identifier from GET /v2/places (place=id:us-n35198-w111651). Supplies lat, lon and, unless tz is also sent, the time zone. Mutually exclusive with sending lat and lon directly. Withheld unless GET /v2 reports capabilities.place_lookup true.",
        "schema": {
          "type": "string"
        },
        "example": "London"
      },
      "PlaceStrategyParam": {
        "name": "place_strategy",
        "in": "query",
        "required": false,
        "description": "best guesses the intended place when a name is ambiguous and reports the guess as a warning. error refuses an ambiguous name with 400 AMBIGUOUS_PLACE and the candidates in `supported`. Only meaningful with `place`.",
        "schema": {
          "type": "string",
          "enum": [
            "best",
            "error"
          ],
          "default": "best"
        },
        "example": "best"
      },
      "EndpointParam": {
        "name": "endpoint",
        "in": "query",
        "required": false,
        "description": "A v2 endpoint path (or its bare slug), to scope the response to that one endpoint.",
        "schema": {
          "type": "string",
          "enum": [
            "/v2",
            "/v2/conventions",
            "/v2/enums",
            "/v2/attribution",
            "/v2/positions",
            "/v2/rise-set",
            "/v2/sun",
            "/v2/moon",
            "/v2/time",
            "/v2/seasons",
            "/v2/apsides",
            "/v2/moon-nodes",
            "/v2/eclipses",
            "/v2/planet-board",
            "/v2/retrogrades",
            "/v2/conjunctions",
            "/v2/separation",
            "/v2/cycles",
            "/v2/twilight",
            "/v2/dark-window",
            "/v2/sidereal-time",
            "/v2/equation-of-time",
            "/v2/libration",
            "/v2/jupiter-moons",
            "/v2/sky-quality",
            "/v2/places",
            "/v2/phases",
            "/v2/today"
          ]
        },
        "example": "/v2/moon"
      },
      "LatitudeParam": {
        "name": "lat",
        "in": "query",
        "required": false,
        "description": "Latitude in decimal degrees, north positive. No compass suffix (do not append N or S). Required together with lon, or neither.",
        "schema": {
          "type": "number",
          "minimum": -90,
          "maximum": 90
        },
        "example": 51.4778
      },
      "LongitudeParam": {
        "name": "lon",
        "in": "query",
        "required": false,
        "description": "Longitude in decimal degrees, east positive. No compass suffix (do not append E or W). Required together with lat, or neither.",
        "schema": {
          "type": "number",
          "minimum": -180,
          "maximum": 180
        },
        "example": -0.0015
      },
      "LatitudeRequiredParam": {
        "name": "lat",
        "in": "query",
        "required": true,
        "description": "Latitude in decimal degrees, north positive. No compass suffix (do not append N or S). Required together with lon, or neither.",
        "schema": {
          "type": "number",
          "minimum": -90,
          "maximum": 90
        },
        "example": 51.4778
      },
      "LongitudeRequiredParam": {
        "name": "lon",
        "in": "query",
        "required": true,
        "description": "Longitude in decimal degrees, east positive. No compass suffix (do not append E or W). Required together with lat, or neither.",
        "schema": {
          "type": "number",
          "minimum": -180,
          "maximum": 180
        },
        "example": -0.0015
      }
    },
    "schemas": {
      "Envelope": {
        "type": "object",
        "description": "The nine-key v2 response envelope. Every /v2 endpoint returns exactly these keys, in this order, on every 200 (spec-00 1.1).",
        "required": [
          "endpoint",
          "computed_at",
          "query",
          "data",
          "warnings",
          "links",
          "meta",
          "attribution",
          "docs"
        ],
        "properties": {
          "endpoint": {
            "type": "string",
            "description": "The endpoint path that produced this response.",
            "example": "/v2/sun"
          },
          "computed_at": {
            "type": "string",
            "format": "date-time",
            "description": "A revision stamp, never a wall clock: identical requests return identical bytes, so a fixed_instant or reference response can be cached for a year and 304 stays reachable.",
            "example": "2026-08-01T00:00:00.000Z"
          },
          "query": {
            "type": "object",
            "description": "The request as understood: every resolved input (instant, location, presentation options) plus `ignored`, in the order spec-00 13.2 defines. Shape is endpoint-specific.",
            "additionalProperties": true
          },
          "data": {
            "description": "The answer. Shape is endpoint-specific; see each operation's summary and /api/reference.html."
          },
          "warnings": {
            "type": "array",
            "description": "Machine-readable notices about this response. Empty when none apply; never changes the HTTP status.",
            "items": {
              "$ref": "#/components/schemas/Warning"
            }
          },
          "links": {
            "type": "object",
            "description": "Related URLs. self reproduces this exact response; docs is always present; others (page, spec, next, first, explain) appear where relevant.",
            "additionalProperties": {
              "type": "string",
              "format": "uri"
            },
            "example": {
              "self": "https://www.cyclecalcs.com/v2/sun?at=2026-06-21&lat=51.4778&lon=-0.0015",
              "docs": "https://www.cyclecalcs.com/api/reference.html#sun",
              "spec": "https://www.cyclecalcs.com/v2/openapi.json",
              "page": "https://www.cyclecalcs.com/sunrise-sunset-calculator.html",
              "explain": "https://www.cyclecalcs.com/learn/seasons.html"
            }
          },
          "meta": {
            "type": "object",
            "description": "Provenance: engine version, rights and attribution, cache class, accuracy statement, tzdb version, and endpoint-specific conventions (refraction model, horizon definitions, time scales, and so on). Never subject to `fields` projection.",
            "additionalProperties": true
          },
          "attribution": {
            "type": "string",
            "description": "The credit line, byte-identical to meta.attribution.text.",
            "example": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only."
          },
          "docs": {
            "type": "string",
            "format": "uri",
            "description": "The human-readable reference section for this endpoint.",
            "example": "https://www.cyclecalcs.com/api/reference.html#sun"
          }
        }
      },
      "Warning": {
        "type": "object",
        "description": "One machine-readable notice. Exactly four keys, in this order (spec-00 11.4). Never changes the HTTP status.",
        "required": [
          "code",
          "message",
          "field",
          "value"
        ],
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "ambiguous_local_time",
              "antipodal_pair_not_aspected",
              "approximates_undefined_placidus",
              "assumed_utc",
              "birth_time_unknown",
              "blocked_layer_omitted",
              "cusps_not_monotonic",
              "dataset_stale",
              "decorative_starfield",
              "dms_rounds_to_next_sign",
              "dst_discontinuity",
              "duplicate_alarm",
              "duplicate_aspect",
              "duplicate_body",
              "duplicate_depression",
              "duplicate_derived",
              "duplicate_house_system",
              "duplicate_include",
              "duplicate_pair",
              "duplicate_parameter",
              "duplicate_point",
              "duplicate_shower",
              "duplicate_source",
              "elevation_ignored",
              "estimate_only",
              "extrapolated_epoch",
              "field_blocked",
              "fields_ignored",
              "harmonic_orb_mode_has_no_effect",
              "high_latitude_caution",
              "horizon_overridden",
              "house_system_substituted",
              "large_intercept",
              "lot_term_null",
              "low_altitude_precision",
              "mean_node_model",
              "model_disagreement",
              "model_epoch_only",
              "moon_dominates",
              "no_astronomical_darkness",
              "no_location_orientation",
              "no_search_result",
              "nonexistent_local_time_shifted",
              "not_visible_from_location",
              "oblateness_simplified",
              "orbit_arc_truncated",
              "parameter_ignored",
              "perfection_search_truncated",
              "place_ambiguous",
              "place_index_tier_2",
              "place_matched_by_substring",
              "placement_within_error_of_boundary",
              "polar_angles_flipped",
              "polar_day",
              "polar_night",
              "polar_region",
              "prose_window_differs",
              "refraction_below_cutoff",
              "render_degenerate",
              "rounded_parameter",
              "sect_tests_disagree",
              "shape_ignored",
              "source_window_clipped",
              "station_near_window_edge",
              "station_search_truncated",
              "time_format_not_applicable",
              "time_scale_undefined",
              "timezone_from_place",
              "truncated_results",
              "tz_source_boundary_1970",
              "tzdb_lmt_interval",
              "unknown_parameter",
              "unusual_navigational_body",
              "vertex_undefined_at_equator"
            ],
            "description": "A stable, lower_snake_case token. The full registry is at GET /v2/enums?set=warning_codes."
          },
          "message": {
            "type": "string",
            "description": "A human-readable explanation. Not parsed by machines; use `code`."
          },
          "field": {
            "type": [
              "string",
              "null"
            ],
            "description": "The data or query pointer the warning is about, dot-addressed the same way `fields` is. Null when the warning is not about one field."
          },
          "value": {
            "description": "The value involved, when there is a single obvious one to show. Null otherwise."
          }
        }
      },
      "Problem": {
        "type": "object",
        "description": "An RFC 9457 problem detail document. Served on every non-2xx v2 response with Content-Type application/problem+json.",
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "code",
          "parameter",
          "hint",
          "example",
          "supported",
          "request_id",
          "error_chain",
          "docs"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "A URI identifying the error type, ending in a #fragment matching `code` in kebab-case. Dereferenceable at /api/errors.html.",
            "example": "https://www.cyclecalcs.com/api/errors.html#bad-latitude"
          },
          "title": {
            "type": "string",
            "description": "A short, fixed, human-readable summary of the error type (not the instance).",
            "example": "Latitude is not a number from -90 to 90"
          },
          "status": {
            "type": "integer",
            "enum": [
              400,
              401,
              404,
              405,
              406,
              409,
              410,
              413,
              414,
              422,
              429,
              500,
              503
            ],
            "description": "The HTTP status code, repeated from the response line.",
            "example": 400
          },
          "detail": {
            "type": "string",
            "description": "A human-readable explanation specific to this occurrence.",
            "example": "lat must be a number from -90 to 90; got \"999\"."
          },
          "instance": {
            "type": "string",
            "format": "uri",
            "description": "Present only on non-cacheable statuses (429, 503, and unexpected 500s): `type` with ?request_id=<id> inserted before its #fragment.",
            "example": "https://www.cyclecalcs.com/api/errors.html?request_id=8f8969364fa1e42f#rate-limited"
          },
          "code": {
            "type": "string",
            "enum": [
              "AMBIGUOUS_LOCAL_TIME",
              "AMBIGUOUS_LONGITUDE_FORMAT",
              "AMBIGUOUS_PLACE",
              "BAD_ALARM",
              "BAD_APERTURE",
              "BAD_BOOLEAN",
              "BAD_BORTLE",
              "BAD_CALENDAR_SOURCE",
              "BAD_CLIENT",
              "BAD_COUNT",
              "BAD_CURSOR",
              "BAD_CYCLE_KEY",
              "BAD_DATE",
              "BAD_DEPRESSION",
              "BAD_ECLIPSE_DIRECTION",
              "BAD_ECLIPSE_TYPE",
              "BAD_ELEVATION",
              "BAD_FIELDS",
              "BAD_FORMAT",
              "BAD_HEIGHT_OF_EYE",
              "BAD_HEMISPHERE",
              "BAD_INCLUDE",
              "BAD_INDEX_ERROR",
              "BAD_LATITUDE",
              "BAD_LIMIT",
              "BAD_LIMITING_MAGNITUDE",
              "BAD_LONGITUDE",
              "BAD_MOON_ALTITUDE",
              "BAD_MOTION",
              "BAD_NELM",
              "BAD_NODE_MODEL",
              "BAD_PAIR",
              "BAD_PAIRS",
              "BAD_PARAMETER_VALUE",
              "BAD_PHASE_SELECTOR",
              "BAD_PLACE",
              "BAD_RADEC",
              "BAD_RANGE",
              "BAD_RANGE_VALUE",
              "BAD_REFRACTION",
              "BAD_SEARCH_HORIZON",
              "BAD_SEXTANT_ALTITUDE",
              "BAD_SIZE",
              "BAD_STEP",
              "BAD_TIMEZONE",
              "BAD_TIME_FORMAT",
              "BODY_NOT_GEOCENTRIC",
              "BODY_NOT_SUPPORTED_HERE",
              "BODY_REQUIRED",
              "BODY_UNAVAILABLE",
              "BUDGET_EXCEEDED",
              "CLUSTER_PARAMETERS_REQUIRED",
              "COMPUTATION_FAILED",
              "COMPUTE_BUDGET_EXCEEDED",
              "CONFLICTING_AYANAMSA_INPUTS",
              "CONFLICTING_PARAMETERS",
              "CONFLICTING_TIME_INPUTS",
              "DATASET_UNAVAILABLE",
              "DATE_OUT_OF_RANGE",
              "ENDPOINT_BLOCKED",
              "ENDPOINT_RETIRED",
              "ENDPOINT_WITHDRAWN",
              "EXPIRED_KEY",
              "FIELD_NOT_COMPUTED",
              "HOUSE_SYSTEM_UNDEFINED",
              "INCOMPLETE_LOCATION",
              "INCOMPLETE_RANGE",
              "INTERNAL_ERROR",
              "INVALID_CHARACTER",
              "INVALID_KEY",
              "INVALID_LOT_EXPRESSION",
              "INVALID_ORB_TABLE",
              "LATITUDE_AT_POLE",
              "LATITUDE_TOO_EXTREME",
              "LOCATION_REQUIRED",
              "LONGITUDE_REQUIRED",
              "METHOD_NOT_ALLOWED",
              "NONEXISTENT_LOCAL_TIME",
              "NOT_ACCEPTABLE",
              "OBJECT_NOT_FOUND",
              "OUTSIDE_MODEL_RANGE",
              "PARAMETER_TOO_LONG",
              "PERFECTION_BUDGET_EXCEEDED",
              "PERFECTION_WINDOW_TOO_LARGE",
              "PERFECTION_WINDOW_UNSUPPORTED_COMBINATION",
              "PLACE_INDEX_UNAVAILABLE",
              "PLACE_NOT_FOUND",
              "QUERY_TOO_SHORT",
              "RANGE_TOO_LARGE",
              "RATE_LIMITED",
              "RESPONSE_TOO_LARGE",
              "REVOKED_KEY",
              "SERVICE_UNAVAILABLE",
              "SKY_CLASS_REQUIRED",
              "STAR_POOL_TIMEOUT",
              "STAR_SLOT_FAILED",
              "TIMEZONE_AUTO_UNSUPPORTED",
              "TIMEZONE_DATABASE_UNAVAILABLE",
              "TIMEZONE_REQUIRED",
              "TIME_REQUIRED",
              "TIME_REQUIRED_FOR_HOUSES",
              "TOO_MANY_BODIES",
              "TOO_MANY_CALENDAR_SOURCES",
              "TOO_MANY_DEPRESSIONS",
              "TOO_MANY_PAIRS",
              "TOO_MANY_PARAMETERS",
              "TOO_MANY_SHOWERS",
              "UNKNOWN_ASPECT",
              "UNKNOWN_AYANAMSA",
              "UNKNOWN_BODY",
              "UNKNOWN_DERIVED",
              "UNKNOWN_ENDPOINT",
              "UNKNOWN_HOUSE_SYSTEM",
              "UNKNOWN_LOT_TERM",
              "UNKNOWN_POINT",
              "UNKNOWN_SHOWER",
              "URI_TOO_LONG"
            ],
            "description": "The stable, SCREAMING_SNAKE_CASE error code a client should branch on. The full registry is at GET /v2/enums?set=error_codes.",
            "example": "BAD_LATITUDE"
          },
          "parameter": {
            "type": [
              "string",
              "null"
            ],
            "description": "The query parameter most responsible for this error, when there is one.",
            "example": "lat"
          },
          "hint": {
            "type": [
              "string",
              "null"
            ],
            "description": "A short suggestion for fixing the request.",
            "example": "Use a signed decimal, for example lat=51.5 for north and lat=-51.5 for south. Do not append N or S"
          },
          "example": {
            "type": [
              "string",
              "null"
            ],
            "description": "A worked example URL or value, when one is more useful than the hint alone."
          },
          "supported": {
            "type": [
              "array",
              "null"
            ],
            "items": {},
            "description": "The accepted values for the offending parameter, when the error is about picking from a closed set."
          },
          "request_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "16 lowercase hex characters, present only on non-cacheable statuses. Quote it when reporting a 500.",
            "example": "8f8969364fa1e42f"
          },
          "error_chain": {
            "type": [
              "array",
              "null"
            ],
            "items": {},
            "description": "For an error caused by resolving another parameter first (rare), the sequence of codes that led here."
          },
          "docs": {
            "type": "string",
            "format": "uri",
            "description": "The API reference index.",
            "example": "https://www.cyclecalcs.com/api/reference.html"
          }
        },
        "additionalProperties": true
      }
    }
  }
}
