API Reference

The complete parameter reference for every route of the CycleCalcs v2 astronomy API: 28 endpoints under /v2, each with its full parameter table, a real worked example, the formats it serves and its cache class. For the quick start, the routes-at-a-glance table and the v1-to-v2 migration notes, see the API overview.

On this page

How to read this reference

Every endpoint below is a plain GET under https://www.cyclecalcs.com/v2. No headers and no key are required. Each entry states, in order: the parameters it accepts, with the exact type, default and effect of each; the formats it serves; the cache class its answers carry; and a request captured live against the code in this repository, with its real response.

The Required column reflects only what that one endpoint needs on its own; a handful of routes accept either of two parameters (for example body or bodies), and the parameter's own description says so. Every parameter not listed for a route is simply not part of that route's contract: sending it is not an error, but it is ignored and named in query.ignored with a warning, exactly as an unrecognised parameter is.

Shared parameters and conventions

These parameters mean the same thing everywhere they are accepted; each endpoint's own table below lists only the ones it actually takes.

ParameterTypeDefaultDescription
atISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
stepduration: 1min, 1h, 6h, 1d, 7d, 1mo, 1yendpoint-specificThe stride of a range query, from 1 minute to 1 year. Only meaningful together with start.
countintegerendpoint-specificHow many rows to return from start. Mutually exclusive with end.
latdecimal degrees, -90 to 90(none unless place resolves it)Latitude, north positive. Sent together with lon (or neither), unless place supplies both. Do not append N or S.
londecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mmetres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placestring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategybest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tzIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousfirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistenterror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
day_anchorcivil | solar | utccivil if tz or place set a zone, else solarWhat midnight means when a day boundary is needed. civil: local midnight in the effective IANA zone, with the historical offset for that date. solar: local mean solar midnight from longitude alone, no timezone or DST. utc: UTC midnight.
time_formatiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatenumjsonResponse format. This endpoint serves: json, ....
fieldscomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisioninteger, 0 to 96Decimal places for numeric output.
pretty0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbosityfull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitintegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.
cursoropaque token(first page)Continues a paginated list from the previous response's links.next, copied unchanged. Stateless and unsigned; a malformed or foreign cursor returns 400 BAD_CURSOR.
refractionnormal | nonenormalnormal applies the Saemundsson refraction model near the horizon; none reports the geometric, unrefracted altitude. Both are always published side by side in the response regardless of this setting.
framesminimal | standard | allstandardHow many coordinate frames to publish per position. minimal: equatorial only. standard: adds ecliptic, and horizontal (alt/az) when a location is given. all: adds galactic, and horizontal always.
depressionscomma-separated degrees, 0 to 30 (up to 8)(none: only the standard bands)Extra Sun-depression angles below the horizon to compute crossing times for, beyond the standard civil/nautical/astronomical bands, for example depressions=17,18.

Three more are accepted on every route without being listed in any table: client (up to 64 characters of your own identifier, echoed back, never affecting the answer), key (reserved for the paid tiers, which are not on sale yet; accepted and currently ignored, with a warning, since capabilities.api_keys is false), and any parameter beginning with _, which is silently accepted as a cache-busting token and never echoed.

CORS is open on every route:

Access-Control-Allow-Origin*
Access-Control-Allow-MethodsGET, HEAD, OPTIONS
Access-Control-Allow-HeadersContent-Type, Authorization, If-None-Match
Access-Control-Expose-HeadersETag, RateLimit, RateLimit-Policy, Retry-After, X-Request-Id, Server-Timing, Link
Access-Control-Max-Age86400

The anonymous tier (the only tier today) is rate-limited to the policy published in the RateLimit-Policy header on every response:

"anonymous";q=300;w=60, "anonymous-hour";q=2000;w=3600, "anonymous-day";q=5000;w=86400

A response also carries an ETag; sending it back as If-None-Match gets a 304 Not Modified with no body, at no cost against your rate limit. Every error is an RFC 9457 problem document; see the error catalogue for the full code list. The accuracy statement behind every position is at the accuracy page, and how the contract itself is versioned is at the versioning page. The machine-readable contract is /v2/openapi.json.

The envelope and meta

Every 2xx response is one JSON object with exactly nine keys, in this order: endpoint (the path that answered), computed_at (a revision stamp, not a wall-clock read), query (the effective, alias-resolved parameters this request used), data (the answer; shape is endpoint-specific), warnings (an array, possibly empty, never absent), links (at least self), meta (provenance, described below), attribution (a plain string, byte-identical to meta.attribution.text), and docs (this page, at this endpoint's own anchor).

meta carries ten keys on every response (api_version, contract_version, engine, tzdb, rights, attribution, notice_url, cache, accuracy, budget) plus whichever conditional sub-blocks that endpoint's answer actually depends on (for example horizon, refraction, time, method, not_computed). A sub-block that does not apply is simply absent, never null. Because this shape is identical everywhere, the worked example under each endpoint below collapses meta to a pointer back here rather than repeating it 28 times; two real examples follow, one for the ordinary case and one for the single route with a licence condition attached.

meta on an ordinary (unrestricted) response, from the /v2/sun example below:

{
  "api_version": "2",
  "contract_version": "2.0.0",
  "engine": {
    "name": "Astronomy Engine",
    "license": "MIT",
    "author": "Don Cross",
    "version": "2.1.19",
    "version_source": "upstream_sha256_match",
    "sha256": "068f1445ed0c636c94818fe6d20d7d125120e605e0bab9fc4675c3d531be5ad7",
    "vendored": true,
    "credit": "astronomy-engine (MIT, Copyright (c) 2019-2023 Don Cross)"
  },
  "tzdb": "2026b",
  "rights": "unrestricted",
  "attribution": {
    "required": false,
    "text": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
    "sources": []
  },
  "notice_url": "https://www.cyclecalcs.com/methodology.html#data-sources",
  "cache": {
    "class": "fixed_instant",
    "deterministic": true
  },
  "accuracy": {
    "statement": "Positions are arcminute class for the Sun, Moon and planets across 1700 to 2200, degrading toward the ends of that window.",
    "residuals_url": "https://www.cyclecalcs.com/api/accuracy.html"
  },
  "time": {
    "scale_of_input": "UTC",
    "ut1_source": "assumed equal to UTC; no IERS bulletin is loaded, so UT1 minus UTC may differ by up to 0.9 s",
    "delta_t_seconds": 75.4,
    "delta_t_model": "Espenak and Meeus"
  },
  "not_computed": [
    {
      "name": "constellation.genitive",
      "reason": "Needs the reduced constellation index; the generator has not run yet."
    }
  ],
  "horizon": {
    "sunrise_sunset_altitude_deg": -0.8333,
    "sunrise_sunset_definition": "centre of the Sun 50 arcminutes below the true horizon, being about 16 arcminutes of solar semidiameter plus about 34 arcminutes of horizon refraction",
    "civil_twilight_altitude_deg": -6,
    "nautical_twilight_altitude_deg": -12,
    "astronomical_twilight_altitude_deg": -18,
    "horizon_dip_modelled": false,
    "horizon_dip_arcmin_if_modelled": 0,
    "horizon_dip_note": "Observer elevation raises the observer on a locally flat plateau. Dip of the sea horizon is not modelled. The value you are forgoing is 1.753 times the square root of the elevation in metres, in arcminutes."
  },
  "refraction": {
    "model": "Saemundsson (Astronomy Engine normal mode)",
    "verified": true,
    "assumed_temperature_c": null,
    "assumed_pressure_mbar": null,
    "applied_below_horizon_down_to_deg": -1
  },
  "method": {
    "solar_noon": "A true upper transit from the engine hour-angle search, not the midpoint of sunrise and sunset that /v1 returns. The two can differ by tens of seconds, most where the equation of time is changing fastest.",
    "ecliptic_source": "Ecliptic(GeoVector(Sun)), used consistently across v2."
  },
  "budget": {
    "engine_calls": 58
  }
}

meta when a place was resolved (GeoNames data, CC BY 4.0), from the /v2/places example below. This is the only clearance condition in the API: rights becomes attribution_required and the credit travels in meta.attribution.text and in the top-level attribution string, in JSON and in CSV alike:

{
  "api_version": "2",
  "contract_version": "2.0.0",
  "engine": {
    "name": "Astronomy Engine",
    "license": "MIT",
    "author": "Don Cross",
    "version": "2.1.19",
    "version_source": "upstream_sha256_match",
    "sha256": "068f1445ed0c636c94818fe6d20d7d125120e605e0bab9fc4675c3d531be5ad7",
    "vendored": true,
    "credit": "astronomy-engine (MIT, Copyright (c) 2019-2023 Don Cross)"
  },
  "tzdb": "2026b",
  "rights": "attribution_required",
  "attribution": {
    "required": true,
    "text": "Place names and coordinates from GeoNames (geonames.org), CC BY 4.0.",
    "sources": [
      {
        "dataset": "GeoNames place index",
        "holder": "GeoNames",
        "licence": "CC BY 4.0",
        "licence_url": "https://creativecommons.org/licenses/by/4.0/",
        "credit": "Place names and coordinates from GeoNames (geonames.org), CC BY 4.0.",
        "url": "https://www.geonames.org/"
      }
    ]
  },
  "notice_url": "https://www.cyclecalcs.com/methodology.html#data-sources",
  "cache": {
    "class": "reference",
    "deterministic": true
  },
  "accuracy": {
    "statement": "Positions are arcminute class for the Sun, Moon and planets across 1700 to 2200, degrading toward the ends of that window.",
    "residuals_url": "https://www.cyclecalcs.com/api/accuracy.html"
  },
  "method": {
    "fold": "Names are matched case- and accent-insensitively: lowercased, NFD-normalised, combining marks U+0300 to U+036F stripped. \"zurich\" finds the accented Swiss city.",
    "ranking": "exact before prefix before substring, then larger population first, then lower place_id ascending. The last is a total order, so the same query returns the same rows in the same order on every node, forever.",
    "determinism": "A client that wants determinism resolves once through /v2/places, stores the place_id, and sends id: forever after.",
    "aliases": "Where two GeoNames rows share a centroid to within about 55 m they are the same physical place under two names. The more populous keeps the row and the other becomes a searchable alias, so no name was lost to make the identifier unique."
  },
  "budget": {
    "engine_calls": 0
  }
}

Cache classes

Every route publishes one of these classes in meta.cache.class, and each endpoint's table below names the one its worked example actually returned.

ClassWhat it meansCache-Control (browser)Netlify-CDN-Cache-Control (edge)
fixed_instantThis request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable.public, max-age=31536000, immutablepublic, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400
now_relativeThis request is anchored to "now" (no at/start was sent), so the answer is recomputed on a short clock and cached only briefly.public, max-age=30public, durable, s-maxage=60, stale-while-revalidate=3600, stale-if-error=86400
aggregateThis request is anchored to "now" but summarises a wider window (several nights, the current sky), so it is recomputed on a longer clock than a plain now_relative answer.public, max-age=300public, durable, s-maxage=900, stale-while-revalidate=86400, stale-if-error=86400
referenceStatic reference data. It changes only when the API itself changes, not on a schedule, so it is cached longer than a now-relative answer while staying safe to re-fetch.public, max-age=300public, durable, s-maxage=3600, stale-while-revalidate=86400

A deterministic class (fixed_instant, reference) also carries an ETag built from the request and the response body, so a repeated call can be answered 304.

Discovery and meta

GET /v2 Phase 1

The discovery document: every endpoint, parameter, convention and limit.

ParameterRequiredTypeDefaultDescription
formatnoenumjsonResponse format. This endpoint serves: json.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.

Formats: json   Cache class: reference

Static reference data. It changes only when the API itself changes, not on a schedule, so it is cached longer than a now-relative answer while staying safe to re-fetch. Cache-Control: public, max-age=300. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=3600, stale-while-revalidate=86400.

Example request

GET https://www.cyclecalcs.com/v2
Example response (real, captured live)
{
  "endpoint": "/v2",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "format": "json",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "ignored": []
  },
  "data": {
    "name": "CycleCalcs API",
    "api_version": "2",
    "contract_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.",
    "base": "https://www.cyclecalcs.com",
    "openapi": "https://www.cyclecalcs.com/v2/openapi.json",
    "llms_txt": "https://www.cyclecalcs.com/v2/llms-full.txt",
    "terms": "https://www.cyclecalcs.com/api.html#terms",
    "output_rights": "The numbers in a CycleCalcs API response are computed astronomical facts. You may use, store, cache, publish, redistribute and build commercial products on them, without restriction, without a licence and without attribution.",
    "attribution": "https://www.cyclecalcs.com/v2/attribution",
    "frozen_versions": [
      {
        "version": "1",
        "base": "https://www.cyclecalcs.com/v1",
        "status": "frozen",
        "note": "Version 1 is unchanged and will not change. New work happens on /v2."
      }
    ],
    "conventions": {
      "time": "Times are UTC (ISO 8601) unless you send tz=, in which case they carry that zone's offset.",
      "date_range": "Reliable from 1700 to 2200.",
      "location": "lat and lon in decimal degrees, latitude north positive and longitude east positive, plus optional elevation_m in metres. They are required on /v2/sun and /v2/rise-set and optional elsewhere; each endpoint's params list below marks an optional parameter with a trailing question mark.",
      "cors": "Open CORS; safe to call from the browser. No key is needed, and none is accepted yet: every request is served at the anonymous tier, which capabilities.api_keys reports as false.",
      "ranges": "Most endpoints accept start, end and step for a series in one request. Caps are by endpoint shape and caller tier.",
      "conventions_document": "https://www.cyclecalcs.com/v2/conventions"
    },
    "limits": {
      "max_rows_global": 10000,
      "max_bodies_per_request": 20,
      "max_engine_calls_per_request": 250000,
      "max_compute_ms": 8000
    },
    "capabilities": {
      "timezone_resolution": true,
      "place_lookup": true,
      "api_keys": false
    },
    "endpoints": [
      {
        "path": "/v2",
        "summary": "The discovery document: every endpoint, parameter, convention and limit",
        "params": [
          "format?",
          "precision?",
          "pretty?",
          "verbosity?"
        ],
        "formats": [
          "json"
        ]
      },
      {
        "path": "/v2/conventions",
        "summary": "Every convention, threshold, model and limit the API uses, as data",
        "params": [
          "format?",
          "precision?",
          "pretty?",
          "verbosity?"
        ],
        "formats": [
          "json"
        ]
      },
      {
        "path": "/v2/enums",
        "summary": "Every enumerated value the API can emit or accept, as data",
        "params": [
          "set?",
          "endpoint?",
          "format?",
          "precision?",
          "... 2 more rows omitted for length ..."
        ],
        "formats": [
          "json"
        ]
      },
      {
        "path": "/v2/attribution",
        "summary": "Every source behind every endpoint, with its licence and what it obliges a caller to do",
        "params": [
          "endpoint?",
          "fields?",
          "format?",
          "precision?",
          "... 2 more rows omitted for length ..."
        ],
        "formats": [
          "json"
        ]
      },
      "... 24 more rows omitted for length ..."
    ]
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2",
    "docs": "https://www.cyclecalcs.com/api/reference.html#discovery",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/api.html"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#discovery"
}

Some arrays are shortened above for length; the live response returns every row.

GET /v2/conventions Phase 1

Every convention, threshold, model and limit the API uses, as data.

ParameterRequiredTypeDefaultDescription
formatnoenumjsonResponse format. This endpoint serves: json.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.

Formats: json   Cache class: reference

Static reference data. It changes only when the API itself changes, not on a schedule, so it is cached longer than a now-relative answer while staying safe to re-fetch. Cache-Control: public, max-age=300. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=3600, stale-while-revalidate=86400.

Example request

GET https://www.cyclecalcs.com/v2/conventions
Example response (real, captured live)
{
  "endpoint": "/v2/conventions",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "format": "json",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "ignored": []
  },
  "data": {
    "time_scales": {
      "input": "The instant you send is interpreted as UTC, or as local civil time in the zone you send in tz=.",
      "ut1": "Treated as equal to UTC. No IERS bulletin is loaded, so UT1 minus UTC may differ by up to 0.9 s. For dates before about 1960 this is the dominant uncertainty in any rise or set time.",
      "tt": "Terrestrial Time, from the engine's delta-T model.",
      "tai": "From the leap-second table (1972 onward): UTC plus the accumulated leap-second count, published as tai_minus_utc_seconds on /v2/time. Undefined before 1972-01-01 and reported as null there, with a time_scale_undefined warning. This is NOT tt minus the definitional 32.184 s: TT minus TAI is 32.184 s exactly by definition, but tt on this API comes from the engine delta-T model, which can run several seconds away from that figure at the present date. /v2/time discloses the live gap as tt_minus_tai_seconds and delta_t_model_residual_seconds rather than leaving a caller to assume the definitional constant applies to the modelled tt reading.",
      "delta_t_model": "Espenak and Meeus",
      "julian_day_epoch": "JD 0.0 is -4712-01-01T12:00:00 in the proleptic Julian calendar. This is the astronomical Julian Day Number, not the ordinal day-of-year that ERP systems also call a Julian date."
    },
    "frames": {
      "eqj": "Mean equator and equinox of J2000.0, aligned with the ICRF to within milliarcseconds.",
      "eqd": "True equator and equinox of date. Precession and nutation applied.",
      "ect": "True ecliptic and equinox of date. This is the frame tropical sign sectors are measured in.",
      "ecl": "Mean ecliptic and equinox of J2000.0.",
      "hor": "Topocentric horizontal. Azimuth clockwise from true north, 0 to 360. Altitude positive above the horizon.",
      "gal": "IAU 1958 galactic coordinates."
    },
    "corrections": {
      "aberration": {
        "applied": true,
        "note": "Applied to every apparent place."
      },
      "light_travel_time": {
        "applied": true,
        "note": "Applied to every apparent place."
      },
      "nutation": {
        "applied": true,
        "note": "Applied in every of-date frame."
      },
      "topocentric_parallax": {
        "applied": true,
        "note": "Applied whenever lat and lon are supplied. For the Moon this reaches about 1 degree, roughly two lunar diameters."
      },
      "proper_motion": {
        "applied": false,
        "note": "Catalogue positions are the epoch stated in the catalogue index."
      },
      "polar_motion": {
        "applied": false,
        "note": "The effect is milliarcsecond class and is an explicit non-goal."
      }
    },
    "refraction": {
      "model": "Saemundsson (Astronomy Engine normal mode)",
      "formula": "1.02 / tan(h + 10.3 / (h + 5.11)) arcminutes, h in degrees, standard atmosphere",
      "verified": true,
      "assumed_temperature_c": null,
      "assumed_pressure_mbar": null,
      "applied_below_horizon_down_to_deg": -1,
      "behaviour_below_cutoff": "Below the cutoff the refraction term is tapered linearly to zero at the nadir rather than extrapolated, so an apparent altitude below the horizon is a stated convention and never a runaway value. Apparent altitude below the horizon is a convention, not a physical quantity.",
      "atmosphere_model": "U.S. Standard Atmosphere (1976), engine Atmosphere(), used only to scale the horizon refraction allowance by relative air density at the observer elevation",
      "near_horizon_refraction_arcmin": 34,
      "how_to_disable": "refraction=none",
      "always_returned": "Every position block returns altitude_refracted_deg, altitude_unrefracted_deg and refraction_deg side by side. An EVENT altitude is a different quantity and is reported once: a rise or set carries the unrefracted value, because that is what the search solved for, and a transit carries the refracted value, because that is what an observer measures. See event_altitude_convention."
    },
    "event_definitions": [
      {
        "code": "sunrise",
        "body": "Sun",
        "altitude_deg": -0.8333,
        "definition": "centre of the Sun 50 arcminutes below the true horizon, being about 16 arcminutes of solar semidiameter plus about 34 arcminutes of horizon refraction"
      },
      {
        "code": "sunset",
        "body": "Sun",
        "altitude_deg": -0.8333,
        "definition": "As sunrise, on the descending crossing."
      },
      {
        "code": "civil_dawn",
        "body": "Sun",
        "altitude_deg": -6,
        "definition": "Centre of the Sun 6 degrees below the horizon, ascending."
      },
      {
        "code": "civil_dusk",
        "body": "Sun",
        "altitude_deg": -6,
        "definition": "Centre of the Sun 6 degrees below the horizon, descending."
      },
      "... 11 more rows omitted for length ..."
    ],
    "event_scopes": [
      {
        "code": "global",
        "definition": "The kind describes the event's greatest character anywhere on Earth, not what an observer at the requested location would see. A solar eclipse that is total somewhere is partial or invisible for almost everyone; local circumstances are a separate computation."
      },
      {
        "code": "local",
        "definition": "The kind and the times are computed for the requested location."
      }
    ],
    "photometry": {
      "magnitude": "Apparent visual magnitude. The scale is logarithmic and INVERTED: a smaller number is brighter, and a step of 1 is a factor of about 2.512 in received light. The Sun is about -26.7, the full Moon about -12.7, the faintest naked-eye stars about 6.5, Pluto about 14.4.",
      "model": "Astronomy Engine Illumination(), which includes Saturn's ring contribution",
      "origin": "geocentric, even when lat and lon are supplied: the topocentric correction to brightness is far below the model uncertainty",
      "naked_eye_threshold_magnitude": 6.5
    },
    "event_altitude_convention": "In a rise_set events array, a rise or a set reports the UNREFRACTED altitude that its event_definition names, which is the value the search solved for. A transit reports the REFRACTED altitude, which is what an observer would measure. Position blocks always return both altitudes side by side.",
    "refraction_parameter_scope": "The refraction parameter is accepted by: /v2/positions, /v2/planet-board, /v2/twilight, /v2/libration, /v2/jupiter-moons, /v2/sky-quality. Elsewhere it is an unknown parameter and is warned, not applied. /v2/rise-set in particular cannot switch it: its search crosses a GEOMETRIC altitude rather than evaluating a refraction model, which is why the event altitude it reports is the unrefracted one.",
    "moon_age_convention": "age_days is the elapsed time in days since the real previous new moon, found by the same search that produces next_phases, so the two always agree. It is NOT phase_angle_deg divided by 360 times the mean synodic month: elongation does not advance uniformly, and that mean-rate figure, which some almanacs print, differs by up to about half a day. Where the mean-rate figure is also returned it is named age_days_mean_rate_proxy.",
    "rise_set_statuses": [
      {
        "code": "normal",
        "label": "Rises and sets",
        "definition": "Exactly one rise and exactly one set inside the window."
      },
      {
        "code": "rise_only",
        "label": "Rises, does not set",
        "definition": "One or more rises, no set, inside the window."
      },
      {
        "code": "set_only",
        "label": "Sets, does not rise",
        "definition": "One or more sets, no rise, inside the window."
      },
      {
        "code": "always_up",
        "label": "Above the horizon throughout",
        "definition": "Altitude at or above the event altitude for the whole window, determined by sampling the upper and lower transit."
      },
      "... 2 more rows omitted for length ..."
    ],
    "day_anchors": [
      {
        "code": "civil",
        "definition": "Local midnight in the effective IANA zone, with the historical offset for that date."
      },
      {
        "code": "solar",
        "definition": "Local mean solar midnight implied by longitude alone. No timezone, no DST."
      },
      {
        "code": "utc",
        "definition": "UTC midnight of the resolved UTC calendar date."
      }
    ],
    "date_range": {
      "min_year": 1700,
      "max_year": 2200,
      "note": "Accuracy degrades toward both ends. On /v2/positions, a request within 20 years of either boundary carries an extrapolated_epoch warning. The other date-taking endpoints do not yet emit it, so treat the boundary as your own responsibility there.",
      "time_scale_note": "A time scale that did not exist at the requested instant is reported as null with a time_scale_undefined warning, which is a different fact from being near a range boundary: TAI, for example, is undefined before 1972."
    },
    "limits": {
      "max_rows_global": 10000,
      "max_bodies_per_request": 20,
      "max_engine_calls_per_request": 250000,
      "max_compute_ms": 8000,
      "caller_tier": "anonymous",
      "keyed_tier_reachable": false,
      "keyed_tier_note": "The pro, ultra and mega columns are the documented plan and are not reachable yet. Sending \"key\" is accepted, ignored and disclosed with a parameter_ignored warning. A stronger tier buys a higher rate limit and a larger range cap, never a different answer, and on the largest endpoints the range a response can carry is bounded by size before it is bounded by tier; the refusal names which bound applied.",
      "range_caps_by_shape": [
        {
          "shape": "daily_one_body",
          "unit": "rows",
          "anonymous": 366,
          "pro": 1830,
          "ultra": 3660,
          "mega": 3660,
          "endpoints": [
            "/v2/sun",
            "/v2/moon",
            "/v2/rise-set",
            "/v2/twilight",
            "... 3 more rows omitted for length ..."
          ]
        },
        {
          "shape": "daily_all_bodies",
          "unit": "rows",
          "anonymous": 93,
          "pro": 366,
          "ultra": 366,
          "mega": 366,
          "endpoints": [
            "/v2/planet-board",
            "/v2/cycles"
          ]
        },
        {
          "shape": "subdaily_grid",
          "unit": "days",
          "anonymous": 31,
          "pro": 93,
          "ultra": 186,
          "mega": 186,
          "endpoints": [
            "/v2/positions",
            "/v2/time",
            "/v2/sidereal-time"
          ]
        },
        {
          "shape": "event_list",
          "unit": "events",
          "anonymous": 100,
          "pro": 400,
          "ultra": 500,
          "mega": 500,
          "endpoints": [
            "/v2/seasons",
            "/v2/apsides",
            "/v2/moon-nodes",
            "/v2/eclipses",
            "... 5 more rows omitted for length ..."
          ]
        }
      ],
      "over_cap_behaviour": "A request over a cap is refused with 400 RANGE_TOO_LARGE carrying the cap extension. It is never silently truncated to a shorter 200.",
      "search_windows": [
        {
          "key": "/v2/seasons",
          "max_window_days": 36600,
          "unit": "days",
          "built": true
        },
        {
          "key": "/v2/apsides.moon",
          "max_window_days": 3660,
          "unit": "days",
          "built": true
        },
        {
          "key": "/v2/apsides.planet",
          "max_window_days": 36600,
          "unit": "days",
          "built": true
        },
        {
          "key": "/v2/moon-nodes",
          "max_window_days": 36600,
          "unit": "days",
          "built": true
        },
        "... 13 more rows omitted for length ..."
      ],
      "search_window_note": "A search endpoint is bounded twice and independently: search_windows caps the SPAN SEARCHED and range_caps_by_shape caps the ROWS RETURNED. A request can be inside one and outside the other. Both refuse with 400 RANGE_TOO_LARGE and the cap extension names the unit.",
      "forward_search_exemption": {
        "rule": "spec-00 6.4 check 4 normally refuses count without start, because count sizes a range and a range needs a beginning.",
        "exemption": "An endpoint that SEARCHES FORWARD from a reference instant rather than sampling a grid accepts count with at, or with no time parameter at all, because \"the next three eclipses\" names no range. Such a request reports query.mode as \"range\".",
        "endpoints": [
          "/v2/eclipses"
        ]
      },
      "request_limits": {
        "max_parameter_value_length_chars": 512,
        "max_distinct_parameter_names": 64,
        "max_uri_bytes": 4096,
        "note": "Enforced in params.mjs readQuery(), before any parameter is parsed: PARAMETER_TOO_LONG, TOO_MANY_PARAMETERS and URI_TOO_LONG respectively.",
        "max_place_length_chars": 128,
        "max_place_length_note": "A place value longer than this is refused as PARAMETER_TOO_LONG before place lookup runs. Enforced in places.mjs resolvePlace(), read through params.mjs resolvePlaceParam().",
        "max_client_id_length_chars": 64,
        "client_id_note": "Up to 64 characters from A-Z, a-z, 0-9, dot, underscore and hyphen; anything else is BAD_CLIENT. Enforced in params.mjs parsePresentation().",
        "pagination_limit_max_rows": 10000,
        "pagination_limit_note": "The `limit` page-size parameter accepts 1 up to this maximum; BAD_LIMIT otherwise. This is the page size, independent of the range_caps_by_shape row cap that bounds the underlying query. Enforced in params.mjs parsePagination(), shared by every paginated endpoint."
      },
      "step_grammar": {
        "grammar": "A decimal number immediately followed by one unit token, with no space: for example 15min, 6h, 1d, 2w, 1mo, 1y.",
        "tokens": [
          {
            "token": "min",
            "minutes": 1
          },
          {
            "token": "m",
            "minutes": 1
          },
          {
            "token": "h",
            "minutes": 60
          },
          {
            "token": "d",
            "minutes": 1440
          },
          "... 3 more rows omitted for length ..."
        ],
        "minimum_minutes": 1,
        "maximum_minutes": 525600,
        "note": "The resolved span in minutes must be at least minimum_minutes and at most maximum_minutes; BAD_STEP otherwise. Enforced in params.mjs parseStep(), shared by every step-accepting endpoint except the one override below.",
        "endpoint_override": {
          "endpoint": "/v2/jupiter-moons",
          "minimum_seconds": 10,
          "maximum_seconds": 600,
          "note": "A Galilean contact needs finer resolution than the generic grammar allows and can never need coarser, so this endpoint replaces the generic bound rather than adding to it."
        }
      },
      "parameter_value_limits": [
        {
          "endpoint": "/v2/rise-set",
          "parameter": "horizon",
          "min": -20,
          "max": 20,
          "unit": "deg",
          "note": "Overrides the event altitude this request searches for, in place of the body's own event_definition altitude_deg. Enforced in v2-rise-set.mjs."
        },
        {
          "endpoint": "/v2/rise-set",
          "parameter": "search_horizon_days",
          "min": 1,
          "max": 400,
          "unit": "days",
          "note": "How far past the requested window the search may run to find a real next rise or set. Default 400. Enforced in v2-rise-set.mjs."
        },
        {
          "endpoint": "/v2/moon",
          "parameter": "next_phases",
          "min": 0,
          "max": 24,
          "unit": "phases",
          "note": "How many upcoming phase events accompany the digest. Enforced in v2-moon.mjs."
        },
        {
          "endpoint": "/v2/sun, /v2/twilight",
          "parameter": "depressions (each entry)",
          "min": 0,
          "max": 30,
          "unit": "deg",
          "note": "The depression angle itself, independent of the entry-count cap in PARAMETER_LIST_LIMITS. Enforced identically in v2-sun.mjs and v2-twilight.mjs."
        },
        "... 11 more rows omitted for length ..."
      ],
      "parameter_list_limits": [
        {
          "endpoint": "/v2/sun, /v2/twilight",
          "parameter": "depressions",
          "max_count": 8,
          "unit": "entries",
          "note": "At most 8 custom depression angles per request. Enforced identically in v2-sun.mjs and v2-twilight.mjs."
        },
        {
          "endpoint": "/v2/conjunctions",
          "parameter": "pairs",
          "max_count": 28,
          "unit": "pairs",
          "note": "An explicit pair list. Enforced in v2-conjunctions.mjs."
        },
        {
          "endpoint": "/v2/conjunctions",
          "parameter": "bodies",
          "max_count": 8,
          "unit": "bodies",
          "note": "Capped so the all-pairs expansion of `bodies` cannot exceed the 28-pair limit above: 8 choose 2 is 28. Enforced in v2-conjunctions.mjs."
        }
      ],
      "engine_budget_overrides": [
        {
          "endpoint": "/v2/retrogrades",
          "max_engine_calls": 60000,
          "note": "Enforced in v2-retrogrades.mjs, pre-flight before any astronomy runs."
        },
        {
          "endpoint": "/v2/separation",
          "max_engine_calls": 200000,
          "note": "Enforced in v2-separation.mjs, pre-flight before any astronomy runs. This endpoint also caps its own row count at 500 minima, independent of range_caps_by_shape."
        }
      ]
    },
    "adopted_constants": {
      "body_radius_km": {
        "Sun": 696000,
        "Moon": 1737.4,
        "Mercury": 2439.7,
        "Venus": 6051.8,
        "Mars": 3396.2,
        "Jupiter": 71492,
        "Saturn": 60268,
        "Uranus": 25559,
        "Neptune": 24764,
        "Pluto": 1188.3
      },
      "synodic_month_days": 29.530588853,
      "mean_earth_radius_km": 6371,
      "mean_earth_radius_note": "The scale for distance_earth_radii on /v2/apsides. It is the MEAN radius; body_radius_km holds equatorial radii, which is the right constant for angular size and the wrong one for a distance ratio.",
      "stationary_band_deg_per_day": 0.0005,
      "stationary_band_note": "A planet whose apparent geocentric ecliptic longitude is changing more slowly than this is reported as apparent_motion \"stationary\". It is deliberately narrow: Saturn 18 days before a station moves at -0.0035 deg per day, which is a real motion and not noise, so the band covers only the hours either side of a true station. is_retrograde stays a strict test on the sign of the rate, so the two-state answer is always available beside the three-state one."
    },
    "non_goals": [
      {
        "code": "satellites",
        "reason": "Satellite passes need TLEs that decay within days. Serving them is a permanent data-operations commitment and a stale answer is worse than no answer."
      },
      {
        "code": "minor_planets",
        "reason": "The MIT-licensed Astronomy Engine has no minor-planet ephemeris, and CycleCalcs will not add a copyleft or paid ephemeris to provide one."
      },
      {
        "code": "polar_motion",
        "reason": "Milliarcsecond-class corrections are beyond the accuracy this API claims."
      },
      {
        "code": "interpretation",
        "reason": "This API returns angles, times and names. It attaches no meaning to any of them."
      }
    ]
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/conventions",
    "docs": "https://www.cyclecalcs.com/api/reference.html#conventions",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#conventions"
}

Some arrays are shortened above for length; the live response returns every row.

GET /v2/enums Phase 1 Partially withheld

Every enumerated value the API can emit or accept, as data.

The meteor_showers set is withheld until the meteor catalogue reduction clears its licence review.

ParameterRequiredTypeDefaultDescription
setnoa set name, e.g. bodies, moon_phase_names(every set)Return just one enumerated set by name instead of the full catalogue. An unrecognised name 400s and lists every real name in supported.
endpointnoa /v2 path, e.g. /v2/sun(every endpoint)Return only the sets referenced by one endpoint's parameters, instead of every set the API declares.
formatnoenumjsonResponse format. This endpoint serves: json.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.

Formats: json   Cache class: reference

Static reference data. It changes only when the API itself changes, not on a schedule, so it is cached longer than a now-relative answer while staying safe to re-fetch. Cache-Control: public, max-age=300. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=3600, stale-while-revalidate=86400.

Example request

GET https://www.cyclecalcs.com/v2/enums?set=moon_phase_names
Example response (real, captured live)
{
  "endpoint": "/v2/enums",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "format": "json",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "set": "moon_phase_names",
    "endpoint": null,
    "ignored": []
  },
  "data": {
    "sets": {
      "moon_phase_names": [
        {
          "code": "new_moon",
          "label": "New Moon",
          "emoji": "🌑",
          "phase_angle_from_deg": 348.75,
          "phase_angle_to_deg": 11.25
        },
        {
          "code": "waxing_crescent",
          "label": "Waxing Crescent",
          "emoji": "🌒",
          "phase_angle_from_deg": 11.25,
          "phase_angle_to_deg": 78.75
        },
        {
          "code": "first_quarter",
          "label": "First Quarter",
          "emoji": "🌓",
          "phase_angle_from_deg": 78.75,
          "phase_angle_to_deg": 101.25
        },
        {
          "code": "waxing_gibbous",
          "label": "Waxing Gibbous",
          "emoji": "🌔",
          "phase_angle_from_deg": 101.25,
          "phase_angle_to_deg": 168.75
        },
        "... 4 more rows omitted for length ..."
      ]
    },
    "notes": {
      "frame_identifiers": "The frame IDENTIFIERS eqj, eqd, ect, ecl, hor and gal are defined at /v2/conventions under frames. They are not accepted values of the frames parameter, which takes the frames_parameter set.",
      "rise_set_special": "rise_set.special is nullable: the two codes in the rise_set_special set are the only non-null values, and null means neither polar condition applies.",
      "known_unreachable": "On warning_codes and error_codes, known_unreachable true means this version cannot produce the code and names the surface it belongs to. False means it is not known to be unreachable, which is weaker than a promise that you can reach it.",
      "hemisphere": "The hemisphere parameter selects the phase EMOJI only. It does not affect limb orientation: that comes from bright_limb.orientation_angle_deg, which is derived from the observer position and is already correct in either hemisphere."
    },
    "set_scope": {
      "moon_phase_names": [
        "/v2/moon",
        "/v2/today"
      ]
    }
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/enums?set=moon_phase_names",
    "docs": "https://www.cyclecalcs.com/api/reference.html#enums",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#enums"
}

Some arrays are shortened above for length; the live response returns every row.

GET /v2/attribution Phase 1

Every source behind every endpoint, with its licence and what it obliges a caller to do.

ParameterRequiredTypeDefaultDescription
endpointnoa /v2 path, e.g. /v2/sun(every endpoint)Return the source and licence rows for just one endpoint instead of every source behind the API.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
formatnoenumjsonResponse format. This endpoint serves: json.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.

Formats: json   Cache class: reference

Static reference data. It changes only when the API itself changes, not on a schedule, so it is cached longer than a now-relative answer while staying safe to re-fetch. Cache-Control: public, max-age=300. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=3600, stale-while-revalidate=86400.

Example request

GET https://www.cyclecalcs.com/v2/attribution?endpoint=/v2/moon
Example response (real, captured live)
{
  "endpoint": "/v2/attribution",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "endpoint": "/v2/moon",
    "format": "json",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "ignored": []
  },
  "data": {
    "rights": "conditional",
    "rights_statement": "The numbers in a CycleCalcs API response are computed astronomical facts. You may use, store, cache, publish, redistribute and build commercial products on them, without restriction, without a licence and without attribution.",
    "rights_note": "Unrestricted for every response that did not resolve a place name. Each response states its own position in meta.rights, which is the authority for that response.",
    "per_response_authority": "meta.rights and meta.attribution on the response itself are authoritative for that response. This document describes what each endpoint CAN draw on.",
    "source_key_note": "Each source carries both license and licence with identical values. spec-00 fixes meta.engine.license and the GeoNames register entry is specified with licence, so the API genuinely contains both spellings; publishing both here means neither is a dead key.",
    "sources": [
      {
        "dataset": "Astronomy Engine",
        "holder": "Don Cross",
        "version": "2.1.19",
        "role": "ephemeris",
        "licence": "MIT",
        "licence_url": "https://github.com/cosinekitty/astronomy/blob/master/LICENSE",
        "credit": "astronomy-engine (MIT, Copyright (c) 2019-2023 Don Cross)",
        "url": "https://github.com/cosinekitty/astronomy",
        "obligation": "None on API output. The MIT notice binds this repository, which carries it with the vendored source, and imposes nothing on a caller who uses the numbers.",
        "conditional": false,
        "license": "MIT",
        "license_url": "https://github.com/cosinekitty/astronomy/blob/master/LICENSE"
      },
      {
        "dataset": "IANA Time Zone Database",
        "holder": "IANA",
        "version": null,
        "role": "time zone offsets and DST transitions",
        "licence": "Public domain",
        "licence_url": "https://www.iana.org/time-zones",
        "credit": "Time zone data from the IANA Time Zone Database (public domain).",
        "url": "https://www.iana.org/time-zones",
        "obligation": "None. The database is dedicated to the public domain. The release that produced a given response is reported in meta.tzdb.",
        "conditional": false,
        "license": "Public domain",
        "license_url": "https://www.iana.org/time-zones"
      },
      {
        "dataset": "GeoNames place index",
        "holder": "GeoNames",
        "licence": "CC BY 4.0",
        "licence_url": "https://creativecommons.org/licenses/by/4.0/",
        "credit": "Place names and coordinates from GeoNames (geonames.org), CC BY 4.0.",
        "url": "https://www.geonames.org/",
        "version": null,
        "role": "place-name lookup",
        "obligation": "Attribution required. A response that resolved a place name carries rights attribution_required and the ready-to-paste credit line in meta.attribution.text; reproduce that line wherever you publish the result.",
        "conditional": true,
        "available": true,
        "unavailable_reason": null,
        "license": "CC BY 4.0",
        "license_url": "https://creativecommons.org/licenses/by/4.0/"
      }
    ],
    "endpoints": {
      "/v2/moon": {
        "rights": "conditional",
        "rights_note": "Unrestricted unless this response resolved a place name, in which case meta.rights on that response reads attribution_required.",
        "sources": [
          "Astronomy Engine",
          "IANA Time Zone Database",
          "GeoNames place index"
        ],
        "conditional_sources": [
          {
            "dataset": "GeoNames place index",
            "available": true,
            "reason": null
          }
        ]
      }
    },
    "generated_from": "netlify/lib/v2/attribution.mjs, derived from the route table in netlify/lib/v2/registry.mjs"
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/attribution?endpoint=%2Fv2%2Fmoon",
    "docs": "https://www.cyclecalcs.com/api/reference.html#attribution",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "notice": "https://www.cyclecalcs.com/methodology.html#data-sources"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#attribution"
}

Sun, Moon, time and today

GET /v2/positions Phase 1

Where every body is, in every frame, with refraction stated.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
stepnoduration: 1min, 1h, 6h, 1d, 7d, 1mo, 1yendpoint-specificThe stride of a range query, from 1 minute to 1 year. Only meaningful together with start.
countnointegerendpoint-specificHow many rows to return from start. Mutually exclusive with end.
bodiesnocomma-separated body names and/or radec: targets, up to 20sun,moon,mercury,venus,mars,jupiter,saturn,uranus,neptune,plutoMultiple targets. Mutually exclusive with body. Duplicates are collapsed with a warning.
bodynoa body name, or radec:<ra>,<dec>[,j2000](see bodies)A single target. Mutually exclusive with bodies.
latnodecimal degrees, -90 to 90(none)Optional. Supplying both lat and lon adds the topocentric (alt/az) view for every body; without a location, positions stay geocentric only.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
framesnominimal | standard | allstandardHow many coordinate frames to publish per position. minimal: equatorial only. standard: adds ecliptic, and horizontal (alt/az) when a location is given. all: adds galactic, and horizontal always.
framenogeocentric | heliocentricgeocentricWhich centre to compute from. heliocentric excludes the Sun (its position relative to itself is undefined) and allows body=earth.
refractionnonormal | nonenormalnormal applies the Saemundsson refraction model near the horizon; none reports the geometric, unrefracted altitude. Both are always published side by side in the response regardless of this setting.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitnointegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.
cursornoopaque token(first page)Continues a paginated list from the previous response's links.next, copied unchanged. Stateless and unsigned; a malformed or foreign cursor returns 400 BAD_CURSOR.

Formats: json, csv   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/positions?bodies=sun,moon&at=2026-07-28T21:30:00Z&lat=51.4778&lon=-0.0015&elevation_m=11&tz=Europe/London
Example response (real, captured live)
{
  "endpoint": "/v2/positions",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "at": "2026-07-28T21:30:00.000Z",
    "mode": "instant",
    "resolved_from": "at",
    "is_now": false,
    "latitude": 51.4778,
    "longitude": -0.0015,
    "elevation_m": 11,
    "tz": "Europe/London",
    "utc_offset_seconds": 3600,
    "utc_offset_iso": "+01:00",
    "is_dst": true,
    "timezone_source": "parameter",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "refraction": "normal",
    "frames": "standard",
    "frame": "geocentric",
    "bodies": [
      "Sun",
      "Moon"
    ],
    "ignored": []
  },
  "data": {
    "instant": "2026-07-28T22:30:00.000+01:00",
    "bodies": [
      {
        "name": "Sun",
        "code": "sun",
        "constellation": {
          "name": "Cancer",
          "abbreviation": "Cnc"
        },
        "tropical_sign": {
          "name": "Leo",
          "code": "leo",
          "sign_index": 4,
          "degree_in_sign": 5.821423,
          "degree_in_sign_whole": 5
        },
        "magnitude": -26.71,
        "position": {
          "equatorial": {
            "j2000": {
              "ra_hours": 8.520516,
              "ra_string": "08h 31m 13.9s",
              "dec_deg": 18.903228,
              "dec_string": "+18 54 11.6"
            },
            "of_date": {
              "ra_hours": 8.546079,
              "ra_string": "08h 32m 45.9s",
              "dec_deg": 18.81321,
              "dec_string": "+18 48 47.6"
            }
          },
          "ecliptic": {
            "true_of_date": {
              "longitude_deg": 125.821033,
              "latitude_deg": -0.00227
            }
          },
          "horizontal": {
            "azimuth_deg": 322.384989,
            "compass": "NW",
            "altitude_refracted_deg": -11.261963,
            "altitude_unrefracted_deg": -11.829866,
            "refraction_deg": 0.567902,
            "hour_angle_hours": 9.391215,
            "parallactic_angle_deg": 23.677847
          },
          "distance_au": 1.015332,
          "distance_km": 151891532.623159,
          "distance_light_minutes": 8.44426,
          "origin": "topocentric",
          "observer_height_m": 11
        },
        "angular_diameter_arcsec": 1890.306919,
        "angular_diameter_basis": "topocentric",
        "elongation_from_sun_deg": 0,
        "ecliptic_separation_from_sun_deg": 0,
        "is_up": false
      },
      {
        "name": "Moon",
        "code": "moon",
        "constellation": {
          "name": "Sagittarius",
          "abbreviation": "Sgr"
        },
        "tropical_sign": {
          "name": "Capricorn",
          "code": "capricorn",
          "sign_index": 9,
          "degree_in_sign": 27.845277,
          "degree_in_sign_whole": 27
        },
        "magnitude": -12.37,
        "position": {
          "equatorial": {
            "j2000": {
              "ra_hours": 20.030443,
              "ra_string": "20h 01m 49.6s",
              "dec_deg": -24.232252,
              "dec_string": "-24 13 56.1"
            },
            "of_date": {
              "ra_hours": 20.057174,
              "ra_string": "20h 03m 25.8s",
              "dec_deg": -24.158146,
              "dec_string": "-24 09 29.3"
            }
          },
          "ecliptic": {
            "true_of_date": {
              "longitude_deg": 297.965852,
              "latitude_deg": -3.666118
            }
          },
          "horizontal": {
            "azimuth_deg": 150.838018,
            "compass": "SSE",
            "altitude_refracted_deg": 9.464498,
            "altitude_unrefracted_deg": 9.36887,
            "refraction_deg": 0.095628,
            "hour_angle_hours": 21.88012,
            "parallactic_angle_deg": -19.427741
          },
          "distance_au": 0.002676,
          "distance_km": 400282.1046,
          "distance_light_minutes": 0.022253,
          "origin": "topocentric",
          "observer_height_m": 11
        },
        "position_geocentric": {
          "equatorial": {
            "j2000": {
              "ra_hours": 20.008848,
              "ra_string": "20h 00m 31.9s",
              "dec_deg": -23.386601,
              "dec_string": "-23 23 11.8"
            },
            "of_date": {
              "ra_hours": 20.035439,
              "ra_string": "20h 02m 07.6s",
              "dec_deg": -23.313229,
              "dec_string": "-23 18 47.6"
            }
          },
          "ecliptic": {
            "true_of_date": {
              "longitude_deg": 297.845277,
              "latitude_deg": -2.778164
            }
          },
          "distance_au": 0.002683,
          "distance_km": 401385.323143,
          "distance_light_minutes": 0.022315,
          "origin": "geocentric"
        },
        "parallax_deg": 0.896077,
        "angular_diameter_arcsec": 1790.565181,
        "angular_diameter_basis": "topocentric",
        "elongation_from_sun_deg": 171.556842,
        "ecliptic_separation_from_sun_deg": 172.01825,
        "is_up": true
      }
    ]
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/positions?at=2026-07-28T21%3A30%3A00Z&bodies=sun%2Cmoon&elevation_m=11&lat=51.4778&lon=-0.0015&tz=Europe%2FLondon",
    "docs": "https://www.cyclecalcs.com/api/reference.html#positions",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/today.html"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#positions"
}
GET /v2/rise-set Phase 1

Rise, transit, set and lower transit for any named object, with an explicit status at every latitude.

ParameterRequiredTypeDefaultDescription
bodyyesa body name, or radec:<ra>,<dec>[,j2000](required unless bodies is sent)A single target. One of body or bodies is required; sending neither is 400 BODY_REQUIRED.
bodiesnocomma-separated body names and/or radec: targets, up to 20(required unless body is sent)Multiple targets, each computed independently. Alternative to body.
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
latyesdecimal degrees, -90 to 90(required)Required; every rise/set event is defined relative to a horizon.
lonyesdecimal degrees, -180 to 180(required)Required alongside lat.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
day_anchornocivil | solar | utccivil if tz or place set a zone, else solarWhat midnight means when a day boundary is needed. civil: local midnight in the effective IANA zone, with the historical offset for that date. solar: local mean solar midnight from longitude alone, no timezone or DST. utc: UTC midnight.
horizonnodegrees, -20 to 20(the body's standard event altitude)Overrides the event altitude used for the rise/set search, for a custom horizon (a mountain skyline, a ship's deck). Disclosed with a warning naming the value used; the override is a true geometric altitude, with no refraction or semidiameter allowance applied.
search_horizon_daysnointeger, 1 to 400400How many days forward the engine searches for an event before giving up, for a body that may not rise or set for a long stretch (polar day/night). Raising it costs compute; the default already crosses any polar night.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitnointegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.
cursornoopaque token(first page)Continues a paginated list from the previous response's links.next, copied unchanged. Stateless and unsigned; a malformed or foreign cursor returns 400 BAD_CURSOR.

Formats: json, csv   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/rise-set?body=moon&at=2026-12-21&lat=69.6492&lon=18.9553&tz=Europe/Oslo
Example response (real, captured live)
{
  "endpoint": "/v2/rise-set",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "at": "2026-12-20T23:00:00.000Z",
    "mode": "instant",
    "resolved_from": "at",
    "is_now": false,
    "latitude": 69.6492,
    "longitude": 18.9553,
    "elevation_m": 0,
    "tz": "Europe/Oslo",
    "utc_offset_seconds": 3600,
    "utc_offset_iso": "+01:00",
    "is_dst": false,
    "timezone_source": "parameter",
    "day_anchor": "civil",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "search_horizon_days": 400,
    "horizon": null,
    "body": "Moon",
    "ignored": []
  },
  "data": {
    "window": {
      "start": "2026-12-21T00:00:00.000+01:00",
      "end": "2026-12-22T00:00:00.000+01:00",
      "anchor": "civil",
      "length_minutes": 1440,
      "local_date": "2026-12-21"
    },
    "body": {
      "name": "Moon",
      "code": "moon",
      "rise_set": {
        "status": "always_up",
        "status_label": "Above the horizon throughout",
        "events": [
          {
            "kind": "lower_transit",
            "instant": "2026-12-21T08:53:11.620+01:00",
            "azimuth_deg": 359.999971,
            "altitude_deg": 1.903805
          },
          {
            "kind": "upper_transit",
            "instant": "2026-12-21T21:23:34.703+01:00",
            "azimuth_deg": 179.999966,
            "altitude_deg": 44.47174
          }
        ],
        "rise": [],
        "set": [],
        "transit": [
          "2026-12-21T21:23:34.703+01:00"
        ],
        "above_horizon_minutes": 1440,
        "next_rise": "2026-12-26T16:11:27.110+01:00",
        "next_set": "2026-12-26T12:40:46.330+01:00"
      },
      "event_altitude_deg": null,
      "event_definition": "upper limb at the horizon, with topocentric parallax and refraction applied by the engine's own rise and set search"
    }
  },
  "warnings": [
    {
      "code": "no_search_result",
      "message": "No rise or set occurs inside this window. The next rise and set outside it are reported as next_rise and next_set.",
      "field": "body.rise_set.events",
      "value": null
    }
  ],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/rise-set?at=2026-12-21&body=moon&lat=69.6492&lon=18.9553&tz=Europe%2FOslo",
    "docs": "https://www.cyclecalcs.com/api/reference.html#rise-set",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/moon-phase-calendar.html"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#rise-set"
}
GET /v2/sun Phase 1

The complete solar day: every named boundary, azimuth, day length and polar status.

ParameterRequiredTypeDefaultDescription
latyesdecimal degrees, -90 to 90(required)Required; every sunrise, sunset and twilight boundary is defined relative to a horizon.
lonyesdecimal degrees, -180 to 180(required)Required alongside lat.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
stepnoduration: 1min, 1h, 6h, 1d, 7d, 1mo, 1yendpoint-specificThe stride of a range query, from 1 minute to 1 year. Only meaningful together with start.
day_anchornocivil | solar | utccivil if tz or place set a zone, else solarWhat midnight means when a day boundary is needed. civil: local midnight in the effective IANA zone, with the historical offset for that date. solar: local mean solar midnight from longitude alone, no timezone or DST. utc: UTC midnight.
depressionsnocomma-separated degrees, 0 to 30 (up to 8)(none: only the standard bands)Extra Sun-depression angles below the horizon to compute crossing times for, beyond the standard civil/nautical/astronomical bands, for example depressions=17,18.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv, txt.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitnointegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.
cursornoopaque token(first page)Continues a paginated list from the previous response's links.next, copied unchanged. Stateless and unsigned; a malformed or foreign cursor returns 400 BAD_CURSOR.

Formats: json, csv, txt   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/sun?lat=51.4778&lon=-0.0015&at=2026-07-27&tz=Europe/London&depressions=17,18
Example response (real, captured live)
{
  "endpoint": "/v2/sun",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "at": "2026-07-26T23:00:00.000Z",
    "mode": "instant",
    "resolved_from": "at",
    "is_now": false,
    "latitude": 51.4778,
    "longitude": -0.0015,
    "elevation_m": 0,
    "tz": "Europe/London",
    "utc_offset_seconds": 3600,
    "utc_offset_iso": "+01:00",
    "is_dst": true,
    "timezone_source": "parameter",
    "day_anchor": "civil",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "depressions": [
      17,
      18
    ],
    "ignored": []
  },
  "data": {
    "window": {
      "start": "2026-07-27T00:00:00.000+01:00",
      "end": "2026-07-28T00:00:00.000+01:00",
      "anchor": "civil",
      "length_minutes": 1440,
      "local_date": "2026-07-27"
    },
    "rise_set": {
      "status": "normal",
      "status_label": "Rises and sets",
      "special": null,
      "events": [
        {
          "kind": "lower_transit",
          "instant": "2026-07-27T01:06:33.985+01:00",
          "azimuth_deg": 359.999871,
          "altitude_deg": -18.760119
        },
        {
          "kind": "rise",
          "instant": "2026-07-27T05:16:24.076+01:00",
          "azimuth_deg": 56.865535,
          "altitude_deg": -0.83332
        },
        {
          "kind": "upper_transit",
          "instant": "2026-07-27T13:06:33.658+01:00",
          "azimuth_deg": 179.999987,
          "altitude_deg": 57.668914
        },
        {
          "kind": "set",
          "instant": "2026-07-27T20:55:46.285+01:00",
          "azimuth_deg": 302.865673,
          "altitude_deg": -0.833301
        }
      ],
      "rise": [
        "2026-07-27T05:16:24.076+01:00"
      ],
      "set": [
        "2026-07-27T20:55:46.285+01:00"
      ],
      "transit": [
        "2026-07-27T13:06:33.658+01:00"
      ],
      "above_horizon_minutes": 939.4
    },
    "solar_noon": "2026-07-27T13:06:33.658+01:00",
    "solar_noon_altitude_deg": 57.668914,
    "solar_noon_altitude_refracted_deg": 57.668914,
    "solar_noon_altitude_unrefracted_deg": 57.658218,
    "solar_noon_azimuth_deg": 179.999987,
    "solar_midnight": "2026-07-27T01:06:33.985+01:00",
    "solar_midnight_altitude_deg": -18.760119,
    "solar_midnight_altitude_refracted_deg": -18.760119,
    "solar_midnight_altitude_unrefracted_deg": -19.273941,
    "day_length": "15h 39m",
    "day_length_minutes": 939.4,
    "day_length_seconds": 56362,
    "day_length_change_from_yesterday_seconds": -173,
    "twilight": {
      "civil": {
        "dawn": "2026-07-27T04:34:35.829+01:00",
        "dusk": "2026-07-27T21:37:19.111+01:00",
        "altitude_deg": -6,
        "definition": "The band runs from -6 to -0.8333 degrees of unrefracted solar altitude. dawn and dusk are the instants the Sun crosses -6 deg, and the two duration fields are the time the Sun spends inside this band, not the time until sunrise or since sunset.",
        "duration_morning_minutes": 41.8,
        "duration_evening_minutes": 41.5
      },
      "nautical": {
        "dawn": "2026-07-27T03:37:13.834+01:00",
        "dusk": "2026-07-27T22:34:05.867+01:00",
        "altitude_deg": -12,
        "definition": "The band runs from -12 to -6 degrees of unrefracted solar altitude. dawn and dusk are the instants the Sun crosses -12 deg, and the two duration fields are the time the Sun spends inside this band, not the time until sunrise or since sunset.",
        "duration_morning_minutes": 57.4,
        "duration_evening_minutes": 56.8
      },
      "astronomical": {
        "dawn": "2026-07-27T02:08:20.099+01:00",
        "dusk": "2026-07-28T00:00:00.615+01:00",
        "altitude_deg": -18,
        "definition": "The band runs from -18 to -12 degrees of unrefracted solar altitude. dawn and dusk are the instants the Sun crosses -18 deg, and the two duration fields are the time the Sun spends inside this band, not the time until sunrise or since sunset.",
        "duration_morning_minutes": 88.9,
        "duration_evening_minutes": 85.9
      }
    },
    "custom": [
      {
        "depression_deg": 17,
        "dawn": "2026-07-27T02:29:19.747+01:00",
        "dusk": "2026-07-27T23:40:16.028+01:00"
      },
      {
        "depression_deg": 18,
        "dawn": "2026-07-27T02:08:20.099+01:00",
        "dusk": "2026-07-28T00:00:00.615+01:00"
      }
    ],
    "golden_hour": {
      "morning": {
        "begin": "2026-07-27T04:51:20.851+01:00",
        "end": "2026-07-27T06:06:20.750+01:00"
      },
      "evening": {
        "begin": "2026-07-27T20:06:02.115+01:00",
        "end": "2026-07-27T21:20:41.036+01:00"
      },
      "definition": "Sun centre from -4 degrees to +6 degrees."
    },
    "blue_hour": {
      "morning": {
        "begin": "2026-07-27T04:34:35.829+01:00",
        "end": "2026-07-27T04:51:20.851+01:00"
      },
      "evening": {
        "begin": "2026-07-27T21:20:41.036+01:00",
        "end": "2026-07-27T21:37:19.111+01:00"
      },
      "definition": "Sun centre from -6 degrees to -4 degrees."
    },
    "night_begins": "2026-07-28T00:00:00.615+01:00",
    "night_ends": "2026-07-28T02:13:34.005+01:00",
    "dark_minutes": 133.6,
    "position_now": {
      "instant": "2026-07-27T00:00:00.000+01:00",
      "basis": "requested_instant",
      "basis_definition": "Evaluated at the instant you asked for.",
      "azimuth_deg": 343.506415,
      "compass": "NNW",
      "altitude_refracted_deg": -17.250728,
      "altitude_unrefracted_deg": -17.775436,
      "refraction_deg": 0.524708,
      "declination_of_date_deg": 19.258682,
      "hour_angle_hours": 10.890574,
      "shadow_length_multiplier": null,
      "is_up": false
    },
    "tropical_sign": {
      "name": "Leo",
      "code": "leo",
      "sign_index": 4,
      "degree_in_sign": 3.970404,
      "degree_in_sign_whole": 3
    },
    "constellation": {
      "name": "Cancer",
      "abbreviation": "Cnc"
    },
    "summary": "The Sun rises at 05:16 and sets at 20:55 local time, giving 15 hours 39 minutes of daylight."
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/sun?at=2026-07-27&depressions=17%2C18&lat=51.4778&lon=-0.0015&tz=Europe%2FLondon",
    "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": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#sun"
}
GET /v2/moon Phase 1

The Moon in render-ready form: phase, illumination, bright limb, libration.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
stepnoduration: 1min, 1h, 6h, 1d, 7d, 1mo, 1yendpoint-specificThe stride of a range query, from 1 minute to 1 year. Only meaningful together with start.
countnointegerendpoint-specificHow many rows to return from start. Mutually exclusive with end.
latnodecimal degrees, -90 to 90(none)Optional. Supplying both lat and lon adds rise, transit and set times for the Moon.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
hemispherenonorth | southsouth if lat < 0, else northWhich hemisphere's emoji and description convention to use for the phase (a waxing crescent looks mirrored from the southern hemisphere). Overrides the latitude-based default when sent explicitly.
next_phasesnointeger, 0 to 244How many upcoming quarter phases (new, first quarter, full, last quarter) to list in data.next_phases.
stylenoname | emoji | sentencenameHow the phase is labelled in the txt format's one-line summary. Has no effect on the JSON field names, which are always present.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
day_anchornocivil | solar | utccivil if tz or place set a zone, else solarWhat midnight means when a day boundary is needed. civil: local midnight in the effective IANA zone, with the historical offset for that date. solar: local mean solar midnight from longitude alone, no timezone or DST. utc: UTC midnight.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv, txt.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitnointegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.
cursornoopaque token(first page)Continues a paginated list from the previous response's links.next, copied unchanged. Stateless and unsigned; a malformed or foreign cursor returns 400 BAD_CURSOR.

Formats: json, csv, txt   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/moon?at=2026-07-27T21:00:00Z&lat=51.4778&lon=-0.0015&tz=Europe/London
Example response (real, captured live)
{
  "endpoint": "/v2/moon",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "at": "2026-07-27T21:00:00.000Z",
    "mode": "instant",
    "resolved_from": "at",
    "is_now": false,
    "latitude": 51.4778,
    "longitude": -0.0015,
    "elevation_m": 0,
    "tz": "Europe/London",
    "utc_offset_seconds": 3600,
    "utc_offset_iso": "+01:00",
    "is_dst": true,
    "timezone_source": "parameter",
    "day_anchor": "civil",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "hemisphere": "north",
    "next_phases": 4,
    "ignored": []
  },
  "data": {
    "phase": {
      "name": "Waxing Gibbous",
      "code": "waxing_gibbous",
      "emoji": "🌔",
      "waxing": true,
      "phase_angle_deg": 160.696581,
      "phase_angle_convention": "MoonPhase: 0 to 360 degrees of elongation of the Moon from the Sun in ecliptic longitude, increasing from 0 at new moon through 180 at full moon.",
      "illumination_phase_angle_deg": 19.582767,
      "illumination_phase_angle_convention": "Illumination().phase_angle: 0 to 180 degrees of Sun-Moon-Earth angle, 0 at full moon and 180 at new moon. This is a different quantity from phase_angle_deg and the two are intentionally both returned.",
      "illuminated_fraction": 0.971079,
      "illumination_percent": 97.1,
      "age_days": 13.47,
      "age_definition": "Elapsed days since the PREVIOUS new moon, found by the same phase search that produces next_phases. next_phases lists only future phases, so it does not contain this instant.",
      "age_days_mean_rate_proxy": 13.18,
      "day_of_cycle": 14,
      "cycle_length_days": 29.53,
      "lunation_number": 1281
    },
    "bright_limb": {
      "position_angle_deg": 250.212861,
      "definition": "Position angle of the midpoint of the bright limb, measured from the celestial north pole eastward, at the topocentric place.",
      "parallactic_angle_deg": -16.632185,
      "orientation_angle_deg": 266.845046,
      "orientation_definition": "position_angle_deg minus parallactic_angle_deg, normalised to [0, 360). It is the angle from the observer zenith direction to the midpoint of the bright limb, measured in the same sense as position_angle_deg, which runs counterclockwise in the observer direct view. The zenith sits at position angle parallactic_angle_deg from the body, so to turn a graphic drawn with celestial north up into the observer view, rotate the graphic CLOCKWISE by parallactic_angle_deg (equivalently, counterclockwise by minus parallactic_angle_deg); that brings the zenith to the top, and the bright limb then sits at orientation_angle_deg from vertical in that view. parallactic_angle_deg is positive when the body is west of the meridian."
    },
    "magnitude": -12.07,
    "distance_km": 402537.664653,
    "distance_au": 0.002691,
    "distance_basis": "topocentric",
    "angular_diameter_arcsec": 1780.531953,
    "fraction_of_mean_distance": 1.049656,
    "fraction_of_mean_distance_definition": "The geocentric Earth-Moon centre distance divided by the mean distance of 384400 km. Geocentric by construction, so it is a property of the orbit and does not change with the observer.",
    "libration": {
      "longitude_deg": -1.393572,
      "latitude_deg": 4.814328,
      "favoured_limb": "west",
      "note": "The value is the sub-Earth selenographic longitude, positive east under the IAU convention. Positive favours the eastern limb, the Mare Crisium side; negative favours the western limb, toward Grimaldi and Mare Orientale. Selenographic east is the opposite side of the disc from sky east. A value within 0.5 deg of zero is reported as none, because no limb is meaningfully favoured there."
    },
    "tropical_sign": {
      "name": "Capricorn",
      "code": "capricorn",
      "sign_index": 9,
      "degree_in_sign": 15.548204,
      "degree_in_sign_whole": 15
    },
    "constellation": {
      "name": "Sagittarius",
      "abbreviation": "Sgr"
    },
    "position_geocentric": {
      "equatorial": {
        "j2000": {
          "ra_hours": 19.128459,
          "ra_string": "19h 07m 42.5s",
          "dec_deg": -26.205724,
          "dec_string": "-26 12 20.6"
        },
        "of_date": {
          "ra_hours": 19.156001,
          "ra_string": "19h 09m 21.6s",
          "dec_deg": -26.163895,
          "dec_string": "-26 09 50.0"
        }
      },
      "ecliptic": {
        "true_of_date": {
          "longitude_deg": 285.548204,
          "latitude_deg": -3.656979
        }
      },
      "distance_au": 0.002697,
      "distance_km": 403487.831539,
      "distance_light_minutes": 0.022432,
      "origin": "geocentric"
    },
    "position_topocentric": {
      "equatorial": {
        "j2000": {
          "ra_hours": 19.147396,
          "ra_string": "19h 08m 50.6s",
          "dec_deg": -27.063727,
          "dec_string": "-27 03 49.4"
        },
        "of_date": {
          "ra_hours": 19.17511,
          "ra_string": "19h 10m 30.4s",
          "dec_deg": -27.021187,
          "dec_string": "-27 01 16.3"
        }
      },
      "ecliptic": {
        "true_of_date": {
          "longitude_deg": 285.700348,
          "latitude_deg": -4.538803
        }
      },
      "horizontal": {
        "azimuth_deg": 155.832838,
        "compass": "SSE",
        "altitude_refracted_deg": 8.076611,
        "altitude_unrefracted_deg": 7.966211,
        "refraction_deg": 0.110401,
        "hour_angle_hours": 22.195103,
        "parallactic_angle_deg": -16.632185
      },
      "distance_au": 0.002691,
      "distance_km": 402537.664653,
      "distance_light_minutes": 0.022379,
      "origin": "topocentric",
      "observer_height_m": 0
    },
    "parallax_deg": 0.894787,
    "window": {
      "start": "2026-07-27T00:00:00.000+01:00",
      "end": "2026-07-28T00:00:00.000+01:00",
      "anchor": "civil",
      "length_minutes": 1440,
      "local_date": "2026-07-27"
    },
    "rise_set": {
      "status": "normal",
      "status_label": "Rises and sets",
      "events": [
        {
          "kind": "set",
          "instant": "2026-07-27T02:22:25.658+01:00",
          "azimuth_deg": 221.893959,
          "altitude_deg": -0.812725
        },
        {
          "kind": "lower_transit",
          "instant": "2026-07-27T11:25:36.503+01:00",
          "azimuth_deg": 359.999926,
          "altitude_deg": -65.724457
        },
        {
          "kind": "rise",
          "instant": "2026-07-27T20:18:10.269+01:00",
          "azimuth_deg": 135.541217,
          "altitude_deg": -0.81339
        },
        {
          "kind": "upper_transit",
          "instant": "2026-07-27T23:50:53.040+01:00",
          "azimuth_deg": 179.999799,
          "altitude_deg": 11.72421
        }
      ],
      "rise": [
        "2026-07-27T20:18:10.269+01:00"
      ],
      "set": [
        "2026-07-27T02:22:25.658+01:00"
      ],
      "transit": [
        "2026-07-27T23:50:53.040+01:00"
      ],
      "above_horizon_minutes": 364.3
    },
    "event_definition": "upper limb at the horizon, with topocentric parallax and refraction applied by the engine's own rise and set search",
    "next_phases": [
      {
        "code": "full_moon",
        "name": "Full Moon",
        "instant": "2026-07-29T15:36:19.011+01:00",
        "days_until": 1.73
      },
      {
        "code": "last_quarter",
        "name": "Last Quarter",
        "instant": "2026-08-06T03:21:58.672+01:00",
        "days_until": 9.22
      },
      {
        "code": "new_moon",
        "name": "New Moon",
        "instant": "2026-08-12T18:37:11.343+01:00",
        "days_until": 15.86
      },
      {
        "code": "first_quarter",
        "name": "First Quarter",
        "instant": "2026-08-20T03:46:58.247+01:00",
        "days_until": 23.24
      }
    ],
    "summary": "The Moon is a waxing gibbous, 97.1 percent lit, 13.5 days into its cycle. It sets at 02:22 and rises at 20:18 local time."
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/moon?at=2026-07-27T21%3A00%3A00Z&lat=51.4778&lon=-0.0015&tz=Europe%2FLondon",
    "docs": "https://www.cyclecalcs.com/api/reference.html#moon",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/moon-phase-calendar.html",
    "explain": "https://www.cyclecalcs.com/learn/moon-phases.html",
    "related": [
      {
        "rel": "cycle",
        "title": "The synodic month",
        "href": "https://www.cyclecalcs.com/cycles/synodic-month.html"
      },
      {
        "rel": "cycle",
        "title": "The anomalistic month",
        "href": "https://www.cyclecalcs.com/cycles/anomalistic-month.html"
      }
    ]
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#moon"
}
GET /v2/time Phase 1

Every time scale, sidereal time, solar time, the equation of time and the Julian dates.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
stepnoduration: 1min, 1h, 6h, 1d, 7d, 1mo, 1yendpoint-specificThe stride of a range query, from 1 minute to 1 year. Only meaningful together with start.
countnointegerendpoint-specificHow many rows to return from start. Mutually exclusive with end.
latnodecimal degrees, -90 to 90(none)Optional. Feeds the Observer used for local apparent solar time and local sidereal time; sent together with lon, or neither.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitnointegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.
cursornoopaque token(first page)Continues a paginated list from the previous response's links.next, copied unchanged. Stateless and unsigned; a malformed or foreign cursor returns 400 BAD_CURSOR.

Formats: json, csv   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/time?at=2026-07-27T12:00:00Z&lat=51.4778&lon=-0.0015
Example response (real, captured live)
{
  "endpoint": "/v2/time",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "at": "2026-07-27T12:00:00.000Z",
    "mode": "instant",
    "resolved_from": "at",
    "is_now": false,
    "latitude": 51.4778,
    "longitude": -0.0015,
    "elevation_m": 0,
    "tz": "UTC",
    "utc_offset_seconds": 0,
    "utc_offset_iso": "+00:00",
    "is_dst": false,
    "timezone_source": "default",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "ignored": []
  },
  "data": {
    "scales": {
      "utc": "2026-07-27T12:00:00.000Z",
      "ut1": "2026-07-27T12:00:00.000Z",
      "ut1_note": "Treated as equal to UTC. No IERS bulletin is loaded, so UT1 minus UTC may differ by up to 0.9 s.",
      "tai": "2026-07-27T12:00:37.000",
      "tt": "2026-07-27T12:01:15.401",
      "scale_reading_note": "utc and ut1 are instants and carry the Z designator. tt and tai are readings of the same instant on their own time scales, so they carry no designator: a trailing Z would assert that the reading is UTC, which it is not. Convert with delta_t_seconds and tai_minus_utc_seconds.",
      "delta_t_seconds": 75.401149,
      "delta_t_model": "Espenak and Meeus",
      "tai_minus_utc_seconds": 37,
      "tai_source": "leap_second_table",
      "tt_source": "engine_delta_t_model",
      "tt_minus_tai_seconds": 38.401149,
      "tt_minus_tai_definition_seconds": 32.184,
      "delta_t_model_residual_seconds": 6.217,
      "delta_t_model_residual_note": "TT minus TAI is 32.184 s by definition. This residual is the delta-T model's departure from the leap-second chain at this instant. TT is reported from the model because that is the time scale the positions were computed at, and TAI from the leap-second table because that is what defines it."
    },
    "julian": {
      "julian_day": 2461249,
      "julian_day_tt": 2461249.0008726986,
      "modified_julian_day": 61248.5,
      "j2000_days": 9704,
      "julian_centuries_j2000": 0.2656810403832991,
      "note": "This is the astronomical Julian Day Number, whose epoch is -4712-01-01T12:00:00 in the proleptic Julian calendar. It is not the ordinal day-of-year that some business systems also call a Julian date."
    },
    "sidereal": {
      "greenwich_apparent_hours": 8.345671,
      "greenwich_apparent_string": "08h 20m 44.4s",
      "greenwich_mean_hours": 8.345511,
      "greenwich_mean_string": "08h 20m 43.8s",
      "equation_of_equinoxes_seconds": 0.575101,
      "local_apparent_hours": 8.345571,
      "local_apparent_string": "08h 20m 44.1s",
      "local_mean_hours": 8.345411,
      "local_mean_string": "08h 20m 43.5s",
      "note": "Greenwich apparent sidereal time comes directly from the engine. Greenwich mean sidereal time is that value minus the equation of the equinoxes. Local values add the observer's east longitude in hours."
    },
    "solar": {
      "mean_solar_time_hours": 11.9999,
      "mean_solar_time_string": "12:00:00",
      "apparent_solar_time_hours": 11.890644,
      "apparent_solar_time_string": "11:53:26",
      "equation_of_time_minutes": -6.555354,
      "equation_of_time_note": "Apparent solar time minus mean solar time. Negative means a sundial reads behind the clock. It is a property of the Sun and the date, not of the observer, so it does not depend on lat or lon.",
      "sun_declination_of_date_deg": 19.139122,
      "sun_greenwich_hour_angle_deg": 358.361161,
      "sun_local_hour_angle_deg": 358.359661,
      "local_apparent_noon": "2026-07-27T12:06:33.658Z"
    }
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/time?at=2026-07-27T12%3A00%3A00Z&lat=51.4778&lon=-0.0015",
    "docs": "https://www.cyclecalcs.com/api/reference.html#time",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/local-sidereal-time.html",
    "explain": "https://www.cyclecalcs.com/learn/sidereal-solar-day.html"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#time"
}
GET /v2/today Phase 1 Partially withheld

The whole sky for a place right now, in one request.

Meteor-shower events are withheld from next_events until the meteor catalogue reduction clears its licence review.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
latnodecimal degrees, -90 to 90(none)Optional. Adds the Sun's rise/set and local context. Without a location the response still covers the Moon, planets and events.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
day_anchornocivil | solar | utccivil if tz or place set a zone, else solarWhat midnight means when a day boundary is needed. civil: local midnight in the effective IANA zone, with the historical offset for that date. solar: local mean solar midnight from longitude alone, no timezone or DST. utc: UTC midnight.
includenocomma-separated: sun, moon, planets, events, nightall fiveWhich sections to include in data. Sending include= (empty) is a 400; omit the parameter for the full set.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, txt.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.

Formats: json, txt   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/today?at=2026-07-28T21:00:00Z&lat=51.4778&lon=-0.0015&tz=Europe/London
Example response (real, captured live)
{
  "endpoint": "/v2/today",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "at": "2026-07-28T21:00:00.000Z",
    "mode": "instant",
    "resolved_from": "at",
    "is_now": false,
    "latitude": 51.4778,
    "longitude": -0.0015,
    "elevation_m": 0,
    "tz": "Europe/London",
    "utc_offset_seconds": 3600,
    "utc_offset_iso": "+01:00",
    "is_dst": true,
    "timezone_source": "parameter",
    "day_anchor": "civil",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "include": [
      "sun",
      "moon",
      "planets",
      "events",
      "... 1 more row omitted for length ..."
    ],
    "ignored": []
  },
  "data": {
    "local_date": "2026-07-28",
    "sun": {
      "rise_set": {
        "status": "normal",
        "status_label": "Rises and sets",
        "special": null,
        "events": [
          {
            "kind": "lower_transit",
            "instant": "2026-07-28T01:06:33.121+01:00",
            "azimuth_deg": 359.999874,
            "altitude_deg": -18.989536
          },
          {
            "kind": "rise",
            "instant": "2026-07-28T05:17:50.647+01:00",
            "azimuth_deg": 57.279351,
            "altitude_deg": -0.833325
          },
          {
            "kind": "upper_transit",
            "instant": "2026-07-28T13:06:32.489+01:00",
            "azimuth_deg": 179.999991,
            "altitude_deg": 57.438623
          },
          {
            "kind": "set",
            "instant": "2026-07-28T20:54:16.609+01:00",
            "azimuth_deg": 302.447462,
            "altitude_deg": -0.833301
          }
        ],
        "rise": [
          "2026-07-28T05:17:50.647+01:00"
        ],
        "set": [
          "2026-07-28T20:54:16.609+01:00"
        ],
        "transit": [
          "2026-07-28T13:06:32.489+01:00"
        ],
        "above_horizon_minutes": 936.4
      },
      "day_length": "15h 36m",
      "day_length_minutes": 936.4,
      "civil_dusk": "2026-07-28T21:35:35.050+01:00",
      "astronomical_dusk": "2026-07-28T23:55:02.016+01:00",
      "is_up": false,
      "altitude_refracted_deg": -8.179033,
      "azimuth_deg": 315.874181
    },
    "moon": {
      "phase": {
        "name": "Full Moon",
        "code": "full_moon",
        "emoji": "🌕",
        "illumination_percent": 99.4,
        "waxing": true,
        "age_days": 14.47
      },
      "constellation": {
        "name": "Sagittarius",
        "abbreviation": "Sgr"
      },
      "rise_set": {
        "status": "normal",
        "status_label": "Rises and sets",
        "events": [
          {
            "kind": "set",
            "instant": "2026-07-28T03:29:09.202+01:00",
            "azimuth_deg": 225.853012,
            "altitude_deg": -0.813717
          },
          {
            "kind": "lower_transit",
            "instant": "2026-07-28T12:15:44.746+01:00",
            "azimuth_deg": 359.999941,
            "altitude_deg": -63.380034
          },
          {
            "kind": "rise",
            "instant": "2026-07-28T20:46:05.756+01:00",
            "azimuth_deg": 130.062322,
            "altitude_deg": -0.814653
          }
        ],
        "rise": [
          "2026-07-28T20:46:05.756+01:00"
        ],
        "set": [
          "2026-07-28T03:29:09.202+01:00"
        ],
        "transit": [],
        "above_horizon_minutes": 403.1
      },
      "is_up": true,
      "altitude_refracted_deg": 7.008975,
      "azimuth_deg": 144.576649,
      "next_full_moon": "2026-07-29T15:36:19.011+01:00",
      "next_new_moon": "2026-08-12T18:37:11.343+01:00"
    },
    "planets_up": [
      {
        "name": "Venus",
        "code": "venus",
        "constellation": {
          "name": "Leo",
          "abbreviation": "Leo"
        },
        "magnitude": -4.29,
        "altitude_refracted_deg": 3.308573,
        "azimuth_deg": 272.336423,
        "elongation_from_sun_deg": 44.992303,
        "visibility": "Evening sky, after dusk",
        "visibility_code": "evening_sky",
        "naked_eye": true
      },
      {
        "name": "Pluto",
        "code": "pluto",
        "constellation": {
          "name": "Capricornus",
          "abbreviation": "Cap"
        },
        "magnitude": 14.45,
        "altitude_refracted_deg": 5.107099,
        "azimuth_deg": 138.356412,
        "elongation_from_sun_deg": 175.452394,
        "visibility": "Up most of the night",
        "visibility_code": "most_of_night",
        "naked_eye": false
      }
    ],
    "planets_down": [
      "Mercury",
      "Mars",
      "Jupiter",
      "Saturn",
      "... 2 more rows omitted for length ..."
    ],
    "night": {
      "dark_start": "2026-07-28T23:55:02.016+01:00",
      "dark_end": "2026-07-29T02:18:30.220+01:00",
      "dark_minutes": 143.5,
      "moonless_dark_minutes": 0,
      "verdict": "Bright. The Moon is up through the whole dark window."
    },
    "next_events": [
      {
        "kind": "full_moon",
        "label": "Full Moon",
        "instant": "2026-07-29T15:36:19.011+01:00",
        "days_until": 0.73,
        "scope": "global",
        "href": "https://www.cyclecalcs.com/full-moon-calendar.html"
      },
      {
        "kind": "new_moon",
        "label": "New Moon",
        "instant": "2026-08-12T18:37:11.343+01:00",
        "days_until": 14.86,
        "scope": "global",
        "href": "https://www.cyclecalcs.com/moon-phase-calendar.html"
      },
      {
        "kind": "solar_eclipse",
        "label": "Total solar eclipse somewhere on Earth",
        "instant": "2026-08-12T18:45:46.794+01:00",
        "days_until": 14.9,
        "scope": "global",
        "href": "https://www.cyclecalcs.com/eclipses/",
        "global_kind": "total"
      },
      {
        "kind": "lunar_eclipse",
        "label": "Partial lunar eclipse",
        "instant": "2026-08-28T05:12:49.076+01:00",
        "days_until": 30.3,
        "scope": "global",
        "href": "https://www.cyclecalcs.com/eclipses/",
        "global_kind": "partial"
      }
    ],
    "summary": "The Moon is full, 99 percent lit. Venus and Pluto are up. The Sun sets at 20:54 and full dark begins at 23:55 local time."
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/today?at=2026-07-28T21%3A00%3A00Z&lat=51.4778&lon=-0.0015&tz=Europe%2FLondon",
    "docs": "https://www.cyclecalcs.com/api/reference.html#today",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/today.html"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#today"
}

Some arrays are shortened above for length; the live response returns every row.

Calendar and cycles

GET /v2/seasons Phase 2

Every equinox and solstice in a window, with the length of each season they bound.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
countnointegerendpoint-specificHow many rows to return from start. Mutually exclusive with end.
latnodecimal degrees, -90 to 90(none)Optional. lon feeds day_anchor=solar (local mean solar midnight); sent together with lat, or neither. Season names come from hemisphere, not from this location.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
hemispherenonorth | southnorthSelects which hemisphere's season NAME applies to each equinox and solstice (the March equinox is spring in the north, autumn in the south). The instant itself is identical everywhere and never changes; a hemisphere_note field says so.
includenocomma-separated: events, seasons, cross_quarters, year_lengthevents, seasonsWhich sections to add to the response. events lists each equinox/solstice; seasons adds the season each one opens with its length; cross_quarters adds the four midpoint days; year_length adds the tropical-year figure.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitnointegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.
cursornoopaque token(first page)Continues a paginated list from the previous response's links.next, copied unchanged. Stateless and unsigned; a malformed or foreign cursor returns 400 BAD_CURSOR.

Formats: json, csv   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/seasons?start=2026-01-01&end=2027-01-01&tz=Europe/London
Example response (real, captured live)
{
  "endpoint": "/v2/seasons",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "start": "2026-01-01T00:00:00.000Z",
    "end": "2027-01-01T00:00:00.000Z",
    "mode": "range",
    "resolved_from": "start",
    "latitude": null,
    "longitude": null,
    "elevation_m": 0,
    "tz": "Europe/London",
    "utc_offset_seconds": 0,
    "utc_offset_iso": "+00:00",
    "is_dst": false,
    "timezone_source": "parameter",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "hemisphere": "north",
    "include": [
      "events",
      "seasons"
    ],
    "ignored": [],
    "rows": 4
  },
  "data": {
    "window": {
      "start": "2026-01-01T00:00:00.000+00:00",
      "end": "2027-01-01T00:00:00.000+00:00",
      "measured_from": "start",
      "length_days": 365
    },
    "hemisphere": "north",
    "hemisphere_note": "Hemisphere selects season NAMES only. Every instant is identical everywhere on Earth, and kind and season_starting_neutral do not change with it.",
    "event_count": 4,
    "events": [
      {
        "kind": "equinox_march",
        "label": "March equinox",
        "instant": "2026-03-20T14:45:36.044+00:00",
        "instant_precision_seconds": 1,
        "body": "Sun",
        "value": null,
        "value_unit": null,
        "days_from_query": 78.6,
        "href": "https://www.cyclecalcs.com/the-sun.html",
        "solar_longitude_deg": 0,
        "solar_longitude_is_definitional": true,
        "season_starting": "Spring",
        "season_starting_neutral": "March equinox season"
      },
      {
        "kind": "solstice_june",
        "label": "June solstice",
        "instant": "2026-06-21T09:25:00.316+01:00",
        "instant_precision_seconds": 1,
        "body": "Sun",
        "value": null,
        "value_unit": null,
        "days_from_query": 171.4,
        "href": "https://www.cyclecalcs.com/the-sun.html",
        "solar_longitude_deg": 90,
        "solar_longitude_is_definitional": true,
        "season_starting": "Summer",
        "season_starting_neutral": "June solstice season"
      },
      {
        "kind": "equinox_september",
        "label": "September equinox",
        "instant": "2026-09-23T01:05:38.617+01:00",
        "instant_precision_seconds": 1,
        "body": "Sun",
        "value": null,
        "value_unit": null,
        "days_from_query": 265,
        "href": "https://www.cyclecalcs.com/the-sun.html",
        "solar_longitude_deg": 180,
        "solar_longitude_is_definitional": true,
        "season_starting": "Autumn",
        "season_starting_neutral": "September equinox season"
      },
      {
        "kind": "solstice_december",
        "label": "December solstice",
        "instant": "2026-12-21T20:50:22.187+00:00",
        "instant_precision_seconds": 1,
        "body": "Sun",
        "value": null,
        "value_unit": null,
        "days_from_query": 354.9,
        "href": "https://www.cyclecalcs.com/the-sun.html",
        "solar_longitude_deg": 270,
        "solar_longitude_is_definitional": true,
        "season_starting": "Winter",
        "season_starting_neutral": "December solstice season"
      }
    ],
    "seasons": [
      {
        "name": "Spring",
        "name_neutral": "March equinox season",
        "starts_with": "equinox_march",
        "ends_with": "solstice_june",
        "start": "2026-03-20T14:45:36.044+00:00",
        "end": "2026-06-21T09:25:00.316+01:00",
        "length_days": 92.7357,
        "ends_inside_window": true
      },
      {
        "name": "Summer",
        "name_neutral": "June solstice season",
        "starts_with": "solstice_june",
        "ends_with": "equinox_september",
        "start": "2026-06-21T09:25:00.316+01:00",
        "end": "2026-09-23T01:05:38.617+01:00",
        "length_days": 93.6532,
        "ends_inside_window": true
      },
      {
        "name": "Autumn",
        "name_neutral": "September equinox season",
        "starts_with": "equinox_september",
        "ends_with": "solstice_december",
        "start": "2026-09-23T01:05:38.617+01:00",
        "end": "2026-12-21T20:50:22.187+00:00",
        "length_days": 89.8644,
        "ends_inside_window": true
      },
      {
        "name": "Winter",
        "name_neutral": "December solstice season",
        "starts_with": "solstice_december",
        "ends_with": "equinox_march",
        "start": "2026-12-21T20:50:22.187+00:00",
        "end": "2027-03-20T20:24:43.453+00:00",
        "length_days": 88.9822,
        "ends_inside_window": false
      }
    ],
    "season_length_definition": "The difference between the two bounding instants, in days to four decimals. It is never the tropical year divided by four and never a count of calendar days.",
    "spread_days": 4.671,
    "spread_note": "The longest season minus the shortest, over the four seasons that START inside this window, each measured over its full length. The seasons are unequal because Earth moves faster near perihelion, and this difference is the visible size of that effect."
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/seasons?end=2027-01-01&start=2026-01-01&tz=Europe%2FLondon",
    "docs": "https://www.cyclecalcs.com/api/reference.html#seasons",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/seasons/",
    "explain": "https://www.cyclecalcs.com/learn/seasons.html"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#seasons"
}
GET /v2/apsides Phase 2

Every perigee, apogee, perihelion and aphelion in a window, with its distance.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
countnointegerendpoint-specificHow many rows to return from start. Mutually exclusive with end.
bodynomoon | earth(see bodies)A single target. earth is accepted here (unlike most endpoints) because a perihelion and aphelion are a well-defined heliocentric orbital fact, not a geocentric position.
bodiesnocomma-separated: moon, earthmoonMultiple targets. moon returns perigee/apogee (distance from Earth); earth returns perihelion/aphelion (distance from the Sun).
latnodecimal degrees, -90 to 90(none unless place resolves it)Latitude, north positive. Sent together with lon (or neither), unless place supplies both. Do not append N or S.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
kindnonear | far | allallnear returns only the near apsis of each target (perigee or perihelion); far only the far one (apogee or aphelion); all returns both.
includenocomma-separated: events, extremes, nearest_syzygy, angular_sizeeventsevents lists every apsis in the window. extremes adds the window's closest and farthest rows. nearest_syzygy adds the nearest new or full moon to each lunar apsis (for supermoon/micromoon context). angular_size adds the apparent diameter at each apsis.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitnointegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.
cursornoopaque token(first page)Continues a paginated list from the previous response's links.next, copied unchanged. Stateless and unsigned; a malformed or foreign cursor returns 400 BAD_CURSOR.

Formats: json, csv   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/apsides?start=2026-01-01&end=2026-04-01&bodies=moon,earth
Example response (real, captured live)
{
  "endpoint": "/v2/apsides",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "start": "2026-01-01T00:00:00.000Z",
    "end": "2026-04-01T00:00:00.000Z",
    "mode": "range",
    "resolved_from": "start",
    "latitude": null,
    "longitude": null,
    "elevation_m": 0,
    "tz": "UTC",
    "utc_offset_seconds": 0,
    "utc_offset_iso": "+00:00",
    "is_dst": false,
    "timezone_source": "default",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "bodies": [
      "Moon",
      "Earth"
    ],
    "kind": "all",
    "include": [
      "events"
    ],
    "ignored": [],
    "rows": 8
  },
  "data": {
    "window": {
      "start": "2026-01-01T00:00:00.000Z",
      "end": "2026-04-01T00:00:00.000Z",
      "measured_from": "start",
      "length_days": 90
    },
    "kind_filter": "all",
    "event_count": 8,
    "events": [
      {
        "kind": "perigee",
        "label": "Moon at perigee, 360,336 km",
        "instant": "2026-01-01T21:44:25.709Z",
        "instant_precision_seconds": 1,
        "body": "Moon",
        "value_km": 360336.382703,
        "value_unit": "km",
        "days_from_query": 0.9,
        "href": "https://www.cyclecalcs.com/the-moon.html",
        "distance_km": 360336.382703,
        "distance_au": 0.002409,
        "distance_earth_radii": 56.558842,
        "distance_earth_radii_basis": "Mean Earth radius 6371 km, published at /v2/conventions."
      },
      {
        "kind": "perihelion",
        "label": "Earth at perihelion, 0.983301 au",
        "instant": "2026-01-03T16:42:09.400Z",
        "instant_precision_seconds": 1,
        "body": "Earth",
        "value_au": 0.983301,
        "value_unit": "au",
        "days_from_query": 2.7,
        "href": "https://www.cyclecalcs.com/the-earth.html",
        "distance_km": 147099797.959882,
        "distance_au": 0.983301
      },
      {
        "kind": "apogee",
        "label": "Moon at apogee, 405,429 km",
        "instant": "2026-01-13T20:46:55.592Z",
        "instant_precision_seconds": 1,
        "body": "Moon",
        "value_km": 405428.59569,
        "value_unit": "km",
        "days_from_query": 12.9,
        "href": "https://www.cyclecalcs.com/the-moon.html",
        "distance_km": 405428.59569,
        "distance_au": 0.00271,
        "distance_earth_radii": 63.636571,
        "distance_earth_radii_basis": "Mean Earth radius 6371 km, published at /v2/conventions."
      },
      {
        "kind": "perigee",
        "label": "Moon at perigee, 365,860 km",
        "instant": "2026-01-29T21:47:50.087Z",
        "instant_precision_seconds": 1,
        "body": "Moon",
        "value_km": 365859.872461,
        "value_unit": "km",
        "days_from_query": 28.9,
        "href": "https://www.cyclecalcs.com/the-moon.html",
        "distance_km": 365859.872461,
        "distance_au": 0.002446,
        "distance_earth_radii": 57.425816,
        "distance_earth_radii_basis": "Mean Earth radius 6371 km, published at /v2/conventions."
      },
      "... 4 more rows omitted for length ..."
    ]
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/apsides?bodies=moon%2Cearth&end=2026-04-01&start=2026-01-01",
    "docs": "https://www.cyclecalcs.com/api/reference.html#apsides",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/supermoons.html",
    "explain": "https://www.cyclecalcs.com/cycles/anomalistic-month.html"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#apsides"
}

Some arrays are shortened above for length; the live response returns every row.

GET /v2/moon-nodes Phase 2

Every crossing of the ecliptic by the Moon, the node longitudes and the eclipse seasons they define.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
countnointegerendpoint-specificHow many rows to return from start. Mutually exclusive with end.
latnodecimal degrees, -90 to 90(none unless place resolves it)Latitude, north positive. Sent together with lon (or neither), unless place supplies both. Do not append N or S.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
node_modelnotrue | mean | bothtruetrue uses the engine's real ephemeris search for the node crossing. mean uses a closed-form analytic approximation (the Meeus polynomial) that can differ from the true node by up to about 1.6 degrees of longitude, several hours of crossing time; a warning discloses this whenever it is used. both returns both, side by side.
kindnoascending | descending | allallFilters to crossings heading north across the ecliptic (ascending), south (descending), or both.
includenocomma-separated: crossings, node_longitude, eclipse_seasons, periods, standstillcrossings, node_longitudecrossings lists every node crossing in the window. node_longitude adds the node's ecliptic longitude at each. eclipse_seasons adds the eclipse-season windows the crossings define. periods adds the nodal-regression period. standstill adds the lunar-standstill context.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitnointegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.
cursornoopaque token(first page)Continues a paginated list from the previous response's links.next, copied unchanged. Stateless and unsigned; a malformed or foreign cursor returns 400 BAD_CURSOR.

Formats: json, csv   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/moon-nodes?start=2026-01-01&end=2027-01-01
Example response (real, captured live)
{
  "endpoint": "/v2/moon-nodes",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "start": "2026-01-01T00:00:00.000Z",
    "end": "2027-01-01T00:00:00.000Z",
    "mode": "range",
    "resolved_from": "start",
    "latitude": null,
    "longitude": null,
    "elevation_m": 0,
    "tz": "UTC",
    "utc_offset_seconds": 0,
    "utc_offset_iso": "+00:00",
    "is_dst": false,
    "timezone_source": "default",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "node_model": "true",
    "kind": "all",
    "include": [
      "crossings",
      "node_longitude"
    ],
    "ignored": [],
    "rows": 27
  },
  "data": {
    "window": {
      "start": "2026-01-01T00:00:00.000Z",
      "end": "2027-01-01T00:00:00.000Z",
      "measured_from": "start",
      "length_days": 365
    },
    "node_model": "true",
    "kind_filter": "all",
    "crossing_count": 27,
    "crossings": [
      {
        "kind": "node_descending",
        "label": "Moon crosses the ecliptic southward, descending node",
        "instant": "2026-01-07T11:22:41.998Z",
        "instant_precision_seconds": 1,
        "body": "Moon",
        "value": null,
        "value_unit": null,
        "days_from_query": 6.5,
        "href": "https://www.cyclecalcs.com/the-moon.html",
        "node_longitude_deg": 160.281958,
        "node_longitude_model": "true"
      },
      {
        "kind": "node_ascending",
        "label": "Moon crosses the ecliptic northward, ascending node",
        "instant": "2026-01-22T00:02:07.969Z",
        "instant_precision_seconds": 1,
        "body": "Moon",
        "value": null,
        "value_unit": null,
        "days_from_query": 21,
        "href": "https://www.cyclecalcs.com/the-moon.html",
        "node_longitude_deg": 339.342683,
        "node_longitude_model": "true"
      },
      {
        "kind": "node_descending",
        "label": "Moon crosses the ecliptic southward, descending node",
        "instant": "2026-02-03T19:18:16.089Z",
        "instant_precision_seconds": 1,
        "body": "Moon",
        "value": null,
        "value_unit": null,
        "days_from_query": 33.8,
        "href": "https://www.cyclecalcs.com/the-moon.html",
        "node_longitude_deg": 159.031379,
        "node_longitude_model": "true"
      },
      {
        "kind": "node_ascending",
        "label": "Moon crosses the ecliptic northward, ascending node",
        "instant": "2026-02-18T06:18:35.971Z",
        "instant_precision_seconds": 1,
        "body": "Moon",
        "value": null,
        "value_unit": null,
        "days_from_query": 48.3,
        "href": "https://www.cyclecalcs.com/the-moon.html",
        "node_longitude_deg": 338.939211,
        "node_longitude_model": "true"
      },
      "... 23 more rows omitted for length ..."
    ],
    "node_longitude": {
      "at": "2026-01-01T00:00:00.000Z",
      "mean_ascending_deg": 342.169207,
      "mean_descending_deg": 162.169207,
      "mean_node_formula": "omega = 125.0445479 - 1934.1362891 T + 0.0020754 T^2 + T^3 / 467441, with T in Julian centuries from J2000.0 TT, wrapped into [0, 360). Meeus, Astronomical Algorithms, chapter 47. The T^4 term of the full series contributes under 3e-7 deg across 1700 to 2200 and is omitted so that this API and the CycleCalcs lunar standstill page return the same number.",
      "regression_rate_deg_per_day": -0.052954,
      "regression_rate_deg_per_year": -19.341352,
      "regression_rate_basis": "The derivative of the mean-node polynomial at this instant, converted from degrees per Julian century to per day. v1 publishes a slightly different rounded linear constant; v2 does not use the linear model at all.",
      "full_cycle_years": 18.6129,
      "true_node_note": "Nutation and the Sun's pull make the true node oscillate about the mean node by up to roughly 1.6 deg, with a period near half an eclipse year. Use the true node for eclipse work and the mean node for long-period cycle arithmetic."
    }
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/moon-nodes?end=2027-01-01&start=2026-01-01",
    "docs": "https://www.cyclecalcs.com/api/reference.html#moon-nodes",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/cycles/lunar-standstill.html",
    "explain": "https://www.cyclecalcs.com/cycles/draconic-month.html"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#moon-nodes"
}

Some arrays are shortened above for length; the live response returns every row.

GET /v2/eclipses Phase 2 Partially withheld

Solar and lunar eclipses with global geometry, local circumstances and an explicit answer to whether you can see it.

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.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
countnointegerendpoint-specificHow many rows to return from start. Mutually exclusive with end.
directionnonext | previous | rangenext (or range if start is sent)next and previous search from at (or now); range lists every eclipse between start and end. Sending start together with direction=next or previous is a 400: start already implies range.
typenosolar | lunar | bothbothRestricts the search to one kind of eclipse.
latnodecimal degrees, -90 to 90(none)Optional. Adds local circumstances (magnitude, obscuration, contact times as seen from here) and, with visible_only, filters the results.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
visible_onlynotrue | falsefalseFilters the results to eclipses visible from the given location. Requires lat and lon; sending it without a location is 400 LOCATION_REQUIRED.
includenocomma-separated: global, local, saros, contacts, greatest, pathglobal, saros (plus local, contacts automatically once a location is given)global is the worldwide geometry. local is the view from lat/lon. saros adds the Saros series number (the member number is withheld; see the enums note below). contacts adds the contact instants. greatest adds the point of greatest eclipse. path adds the ground track for a total or annular solar eclipse.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv, txt.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitnointegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.
cursornoopaque token(first page)Continues a paginated list from the previous response's links.next, copied unchanged. Stateless and unsigned; a malformed or foreign cursor returns 400 BAD_CURSOR.

Formats: json, csv, txt   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/eclipses?at=2026-07-28T21:30:00Z&lat=51.4778&lon=-0.0015&elevation_m=11&count=2
Example response (real, captured live)
{
  "endpoint": "/v2/eclipses",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "count": 2,
    "mode": "range",
    "resolved_from": "at",
    "latitude": 51.4778,
    "longitude": -0.0015,
    "elevation_m": 11,
    "tz": "UTC",
    "utc_offset_seconds": 0,
    "utc_offset_iso": "+00:00",
    "is_dst": false,
    "timezone_source": "default",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "direction": "next",
    "type": "both",
    "visible_only": false,
    "include": [
      "global",
      "saros",
      "local",
      "contacts"
    ],
    "ignored": [],
    "rows": 4
  },
  "data": {
    "window": {
      "at": "2026-07-28T21:30:00.000Z",
      "measured_from": "at",
      "direction": "next"
    },
    "type": "both",
    "eclipse_count": 4,
    "eclipses": [
      {
        "type": "solar",
        "kind": "total",
        "label": "Solar eclipse (total)",
        "peak": "2026-08-12T17:45:46.794Z",
        "peak_precision_seconds": 1,
        "days_from_reference": 14.844292,
        "href": "https://www.cyclecalcs.com/eclipses/",
        "gamma": 0.897578,
        "gamma_definition": "The perpendicular distance from the Earth's centre to the shadow axis, in Earth radii, positive when the axis passes north of the centre. The magnitude comes from the engine's own geoid-aware distance and the sign from the shadow geometry.",
        "obscuration_fraction": 1,
        "obscuration_source": "engine",
        "shadow_axis_distance_km": 5724.875907,
        "greatest_eclipse_latitude_deg": 65.215531,
        "greatest_eclipse_longitude_deg": -25.249464,
        "eclipse_magnitude": 1.039988,
        "eclipse_magnitude_phase": "central",
        "saros": {
          "series": 126,
          "member_number": null,
          "member_number_source": "unverified",
          "member_number_blocked_on": "The member number needs the epoch of member 1 of each of the 223 series, which is catalogue data. The NASA GSFC five-millennium catalogue is the obvious source and is very likely a public-domain United States Government work, but that has not been verified to the standard this project applies to a licence question. It is never guessed.",
          "series_definition": "The van den Bergh series number, computed in closed form. Eclipses one saros apart, about 6585.3211 days, share a series and are geometrically similar."
        },
        "local": {
          "visible": true,
          "visibility_extent": "fully_visible",
          "kind": "partial",
          "eclipse_magnitude": 0.924998,
          "obscuration_fraction": 0.912749,
          "obscuration_source": "engine",
          "maximum_above_horizon": true,
          "best_observable": null,
          "magnitude_definition": "The fraction of the Sun's diameter covered at local maximum, which happens with the Sun above your horizon.",
          "contacts": [
            {
              "kind": "first_contact",
              "instant": "2026-08-12T17:17:16.185Z",
              "altitude_deg": 18.982289,
              "azimuth_deg": 270.163433,
              "above_horizon": true
            },
            {
              "kind": "maximum",
              "instant": "2026-08-12T18:13:13.640Z",
              "altitude_deg": 10.352524,
              "azimuth_deg": 280.903917,
              "above_horizon": true
            },
            {
              "kind": "fourth_contact",
              "instant": "2026-08-12T19:06:11.193Z",
              "altitude_deg": 2.598045,
              "azimuth_deg": 291.023885,
              "above_horizon": true
            }
          ],
          "duration_central_seconds": null,
          "duration_central_phase": null,
          "sun_sets_during_eclipse": false,
          "sun_rises_during_eclipse": false,
          "verdict": "This is a partial eclipse from here, covering 92.5 per cent of the Sun's diameter at maximum, with the Sun 10.4 deg above the horizon.",
          "detectable": true
        }
      },
      {
        "type": "lunar",
        "kind": "partial",
        "label": "Lunar eclipse (partial)",
        "peak": "2026-08-28T04:12:49.076Z",
        "peak_precision_seconds": 1,
        "days_from_reference": 30.279735,
        "href": "https://www.cyclecalcs.com/eclipses/",
        "durations": {
          "penumbral_minutes": 338.4358,
          "partial_minutes": 198.842993,
          "total_minutes": 0
        },
        "gamma": 0.497203,
        "gamma_definition": "The perpendicular distance from the Earth's shadow axis to the Moon, in Earth radii, positive when the Moon passes north of the axis.",
        "umbral_magnitude": 0.936719,
        "penumbral_magnitude": 1.991408,
        "umbral_obscuration_fraction": 0.966056,
        "obscuration_definition": "The fraction of the lunar disc inside the UMBRA, the Earth's inner shadow. It is 0 for a penumbral eclipse by definition, which does not mean nothing happens: see penumbral_magnitude.",
        "obscuration_source": "engine",
        "contacts_utc": [
          {
            "kind": "penumbral_begin",
            "instant": "2026-08-28T01:23:36.002Z"
          },
          {
            "kind": "partial_begin",
            "instant": "2026-08-28T02:33:23.786Z"
          },
          {
            "kind": "maximum",
            "instant": "2026-08-28T04:12:49.076Z"
          },
          {
            "kind": "partial_end",
            "instant": "2026-08-28T05:52:14.366Z"
          },
          "... 1 more row omitted for length ..."
        ],
        "semiduration_penumbral_minutes": 169.2179,
        "semiduration_partial_minutes": 99.421496,
        "semiduration_total_minutes": 0,
        "saros": {
          "series": 138,
          "member_number": null,
          "member_number_source": "unverified",
          "member_number_blocked_on": "The member number needs the epoch of member 1 of each of the 223 series, which is catalogue data. The NASA GSFC five-millennium catalogue is the obvious source and is very likely a public-domain United States Government work, but that has not been verified to the standard this project applies to a licence question. It is never guessed.",
          "series_definition": "The van den Bergh series number, computed in closed form. Eclipses one saros apart, about 6585.3211 days, share a series and are geometrically similar."
        },
        "local": {
          "visible": true,
          "visibility_extent": "partly_visible",
          "moon_altitude_at_maximum_deg": 7.918228,
          "moon_azimuth_at_maximum_deg": 242.908212,
          "visible_contacts": [
            "penumbral_begin",
            "partial_begin",
            "maximum"
          ],
          "contacts": [
            {
              "kind": "penumbral_begin",
              "instant": "2026-08-28T01:23:36.002Z",
              "altitude_deg": 24.856546,
              "azimuth_deg": 204.195731,
              "above_horizon": true
            },
            {
              "kind": "partial_begin",
              "instant": "2026-08-28T02:33:23.786Z",
              "altitude_deg": 19.384738,
              "azimuth_deg": 221.340958,
              "above_horizon": true
            },
            {
              "kind": "maximum",
              "instant": "2026-08-28T04:12:49.076Z",
              "altitude_deg": 7.918228,
              "azimuth_deg": 242.908212,
              "above_horizon": true
            },
            {
              "kind": "partial_end",
              "instant": "2026-08-28T05:52:14.366Z",
              "altitude_deg": -5.545434,
              "azimuth_deg": 262.265512,
              "above_horizon": false
            },
            "... 1 more row omitted for length ..."
          ],
          "moonset_during_eclipse": "2026-08-28T05:15:24.774Z",
          "moonrise_during_eclipse": null,
          "verdict": "Part of this eclipse happens with the Moon below your horizon. visible_contacts lists the phases you can actually see.",
          "note": "A lunar eclipse is one event for the whole night side, so the only local question is whether the Moon is above your horizon at each contact. above_horizon says exactly which parts you lose.",
          "detectable": true
        }
      },
      {
        "type": "solar",
        "kind": "annular",
        "label": "Solar eclipse (annular)",
        "peak": "2027-02-06T15:59:32.957Z",
        "peak_precision_seconds": 1,
        "days_from_reference": 192.77052,
        "href": "https://www.cyclecalcs.com/eclipses/",
        "gamma": -0.294995,
        "gamma_definition": "The perpendicular distance from the Earth's centre to the shadow axis, in Earth radii, positive when the axis passes north of the centre. The magnitude comes from the engine's own geoid-aware distance and the sign from the shadow geometry.",
        "obscuration_fraction": 0.861451,
        "obscuration_source": "engine",
        "shadow_axis_distance_km": 1881.520156,
        "greatest_eclipse_latitude_deg": -31.294889,
        "greatest_eclipse_longitude_deg": -48.465096,
        "eclipse_magnitude": 0.929266,
        "eclipse_magnitude_phase": "central",
        "saros": {
          "series": 131,
          "member_number": null,
          "member_number_source": "unverified",
          "member_number_blocked_on": "The member number needs the epoch of member 1 of each of the 223 series, which is catalogue data. The NASA GSFC five-millennium catalogue is the obvious source and is very likely a public-domain United States Government work, but that has not been verified to the standard this project applies to a licence question. It is never guessed.",
          "series_definition": "The van den Bergh series number, computed in closed form. Eclipses one saros apart, about 6585.3211 days, share a series and are geometrically similar."
        },
        "local": {
          "visible": false,
          "visibility_extent": "not_visible",
          "note": "No part of this eclipse is above the horizon from this location."
        }
      },
      {
        "type": "lunar",
        "kind": "penumbral",
        "label": "Lunar eclipse (penumbral)",
        "peak": "2027-02-20T23:12:44.142Z",
        "peak_precision_seconds": 1,
        "days_from_reference": 207.071344,
        "href": "https://www.cyclecalcs.com/eclipses/",
        "durations": {
          "penumbral_minutes": 241.734403,
          "partial_minutes": 0,
          "total_minutes": 0
        },
        "gamma": -1.048703,
        "gamma_definition": "The perpendicular distance from the Earth's shadow axis to the Moon, in Earth radii, positive when the Moon passes north of the axis.",
        "umbral_magnitude": -0.049316,
        "penumbral_magnitude": 0.953177,
        "umbral_obscuration_fraction": 0,
        "obscuration_definition": "The fraction of the lunar disc inside the UMBRA, the Earth's inner shadow. It is 0 for a penumbral eclipse by definition, which does not mean nothing happens: see penumbral_magnitude.",
        "obscuration_source": "engine",
        "contacts_utc": [
          {
            "kind": "penumbral_begin",
            "instant": "2027-02-20T21:11:52.110Z"
          },
          {
            "kind": "maximum",
            "instant": "2027-02-20T23:12:44.142Z"
          },
          {
            "kind": "penumbral_end",
            "instant": "2027-02-21T01:13:36.174Z"
          }
        ],
        "semiduration_penumbral_minutes": 120.867202,
        "semiduration_partial_minutes": 0,
        "semiduration_total_minutes": 0,
        "saros": {
          "series": 143,
          "member_number": null,
          "member_number_source": "unverified",
          "member_number_blocked_on": "The member number needs the epoch of member 1 of each of the 223 series, which is catalogue data. The NASA GSFC five-millennium catalogue is the obvious source and is very likely a public-domain United States Government work, but that has not been verified to the standard this project applies to a licence question. It is never guessed.",
          "series_definition": "The van den Bergh series number, computed in closed form. Eclipses one saros apart, about 6585.3211 days, share a series and are geometrically similar."
        },
        "local": {
          "visible": true,
          "visibility_extent": "fully_visible",
          "moon_altitude_at_maximum_deg": 45.915202,
          "moon_azimuth_at_maximum_deg": 158.575906,
          "visible_contacts": [
            "penumbral_begin",
            "maximum",
            "penumbral_end"
          ],
          "contacts": [
            {
              "kind": "penumbral_begin",
              "instant": "2027-02-20T21:11:52.110Z",
              "altitude_deg": 34.766651,
              "azimuth_deg": 122.969536,
              "above_horizon": true
            },
            {
              "kind": "maximum",
              "instant": "2027-02-20T23:12:44.142Z",
              "altitude_deg": 45.915202,
              "azimuth_deg": 158.575906,
              "above_horizon": true
            },
            {
              "kind": "penumbral_end",
              "instant": "2027-02-21T01:13:36.174Z",
              "altitude_deg": 45.453944,
              "azimuth_deg": 200.773319,
              "above_horizon": true
            }
          ],
          "moonset_during_eclipse": null,
          "moonrise_during_eclipse": null,
          "verdict": "The whole eclipse happens with the Moon above your horizon, 45.9 deg up at maximum.",
          "note": "A lunar eclipse is one event for the whole night side, so the only local question is whether the Moon is above your horizon at each contact. above_horizon says exactly which parts you lose.",
          "detectable": true
        }
      }
    ],
    "next_visible": {
      "solar": {
        "same_as_next_global": true,
        "peak": "2026-08-12T18:13:13.640Z",
        "kind": "partial",
        "altitude_deg": 10.352524,
        "maximum_above_horizon": true,
        "obscuration_fraction": 0.912749,
        "eclipse": null
      },
      "lunar": {
        "same_as_next_global": true,
        "peak": "2026-08-28T04:12:49.076Z",
        "kind": "partial",
        "altitude_deg": 7.918226
      }
    },
    "next_visible_note": "A solar eclipse reaches only part of the Earth, so the next eclipse ANYWHERE is usually not the next one you can see. The eclipses array answers the first question and this answers the second.",
    "summary": "The next solar eclipse is on 2026-08-12 and is total where the shadow axis falls. From your location it is partial, covering 92 per cent of the Sun's diameter."
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/eclipses?at=2026-07-28T21%3A30%3A00Z&count=2&elevation_m=11&lat=51.4778&lon=-0.0015",
    "docs": "https://www.cyclecalcs.com/api/reference.html#eclipses",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/eclipses/",
    "explain": "https://www.cyclecalcs.com/learn/eclipses.html"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#eclipses"
}

Some arrays are shortened above for length; the live response returns every row.

GET /v2/phases Phase 2

The lunar phase calendar: every quarter with its distance, apparent size, supermoon classification under BOTH competing rules, traditional name and any eclipse.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
countnointegerendpoint-specificHow many rows to return from start. Mutually exclusive with end.
phasesnocomma-separated: new_moon, first_quarter, full_moon, last_quarterall fourRestricts the calendar to specific quarters.
latnodecimal degrees, -90 to 90(none unless place resolves it)Latitude, north positive. Sent together with lon (or neither), unless place supplies both. Do not append N or S.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
day_anchornocivil | solar | utccivil if tz or place set a zone, else solarWhat midnight means when a day boundary is needed. civil: local midnight in the effective IANA zone, with the historical offset for that date. solar: local mean solar midnight from longitude alone, no timezone or DST. utc: UTC midnight.
includenocomma-separated: distance, classification, names, eclipse, libration, rise_setdistance, classification, names, eclipsedistance adds the Earth-Moon distance and apparent diameter. classification adds the supermoon/micromoon verdict under both competing rules. names adds the traditional full-moon name where one applies. eclipse adds any eclipse tied to that syzygy. libration and rise_set need a location and add the sub-Earth point and local rise/set respectively.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv, txt.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitnointegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.
cursornoopaque token(first page)Continues a paginated list from the previous response's links.next, copied unchanged. Stateless and unsigned; a malformed or foreign cursor returns 400 BAD_CURSOR.

Formats: json, csv, txt   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/phases?start=2026-07-29&end=2026-08-31&tz=Europe/London
Example response (real, captured live)
{
  "endpoint": "/v2/phases",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "start": "2026-07-28T23:00:00.000Z",
    "end": "2026-08-30T23:00:00.000Z",
    "mode": "range",
    "resolved_from": "start",
    "latitude": null,
    "longitude": null,
    "elevation_m": 0,
    "tz": "Europe/London",
    "utc_offset_seconds": 3600,
    "utc_offset_iso": "+01:00",
    "is_dst": true,
    "timezone_source": "parameter",
    "day_anchor": "civil",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "phases": [
      "new_moon",
      "first_quarter",
      "full_moon",
      "last_quarter"
    ],
    "include": [
      "distance",
      "classification",
      "names",
      "eclipse"
    ],
    "rows": 5,
    "ignored": []
  },
  "data": {
    "window": {
      "start": "2026-07-29T00:00:00.000+01:00",
      "end": "2026-08-31T00:00:00.000+01:00",
      "length_days": 33
    },
    "phase_count": 5,
    "phases": [
      {
        "kind": "full_moon",
        "label": "Full Moon",
        "instant": "2026-07-29T15:36:19.011+01:00",
        "instant_precision_seconds": 1,
        "local_date": "2026-07-29",
        "days_from_query": 0.65022,
        "lunation_number": 1281,
        "distance_km": 399605.484864,
        "angular_diameter_arcsec": 1793.59702,
        "fraction_of_mean_distance": 1.039556,
        "apparent_size_vs_mean_percent": -3.805124,
        "apparent_brightness_vs_mean_percent": -7.465459,
        "illuminated_fraction": 0.999673,
        "apparent_magnitude": -12.54,
        "illumination_note": "At an exact quarter the illuminated fraction is about 0.5013 rather than 0.5000. The quarters are defined by the difference in ecliptic longitude, not by half illumination, and the two differ by a few hours of geometry. It is not rounded to look tidy.",
        "classification": {
          "applicable": true,
          "rule": "site",
          "rule_definition": "A supermoon is a phase occurring closer than 360,000 km and a micromoon further than 405,000 km, measured at the instant of the phase.",
          "is_supermoon": false,
          "is_micromoon": false,
          "supermoon_threshold_km": 360000,
          "micromoon_threshold_km": 405000,
          "espenak": {
            "rule_definition": "Espenak's rule, following Nolle: a new or full Moon qualifies when it is closer than ten percent of the way from the closest perigee to the farthest apogee of that CALENDAR YEAR. One threshold applies to the whole year, and it shifts by a few hundred kilometres from year to year as the extreme apsides move.",
            "qualifies": false,
            "threshold_km": 361617.591879,
            "threshold_year": 2026,
            "closest_perigee_of_year_km": 356641.002581,
            "farthest_apogee_of_year_km": 406406.895565
          },
          "criteria_disagree": false,
          "disagreement_note": null
        },
        "name": {
          "traditional_name": "Buck Moon",
          "traditional_name_if_not_blue": "Buck Moon",
          "month_table_name": null,
          "harvest_moon_note": null,
          "is_blue_moon_monthly": false,
          "is_blue_moon_seasonal": false,
          "blue_moon_basis_timezone": "Europe/London",
          "blue_moon_note": "Two senses, both computed. MONTHLY is the second full moon in one calendar month, and whether two full moons share a month genuinely depends on the timezone, which is why the zone is echoed here. SEASONAL is the third full moon in an astronomical season containing four, the Sky and Telescope 1999 reconstruction of Maine Farmers' Almanac practice.",
          "names_note": "Folklore labels, not astronomical terms. They were popularised by the Old Farmer's Almanac, drawing on Native American (notably Algonquian), colonial American and European traditions, and the month a name attaches to is the calendar month in the effective timezone."
        },
        "eclipse": null,
        "href": "https://www.cyclecalcs.com/full-moon-calendar.html"
      },
      {
        "kind": "last_quarter",
        "label": "Last Quarter",
        "instant": "2026-08-06T03:21:58.672+01:00",
        "instant_precision_seconds": 1,
        "local_date": "2026-08-06",
        "days_from_query": 8.140262,
        "lunation_number": 1281,
        "distance_km": 373334.993094,
        "angular_diameter_arcsec": 1919.808079,
        "fraction_of_mean_distance": 0.971215,
        "apparent_size_vs_mean_percent": 2.963828,
        "apparent_brightness_vs_mean_percent": 6.015499,
        "illuminated_fraction": 0.50123,
        "apparent_magnitude": -10.16,
        "illumination_note": "At an exact quarter the illuminated fraction is about 0.5013 rather than 0.5000. The quarters are defined by the difference in ecliptic longitude, not by half illumination, and the two differ by a few hours of geometry. It is not rounded to look tidy.",
        "classification": {
          "applicable": false,
          "not_applicable_reason": "A supermoon or micromoon is by definition a new or full Moon. This is a last quarter, so neither rule applies to it. The distance is reported above either way.",
          "rule": null,
          "is_supermoon": null,
          "is_micromoon": null,
          "espenak": null,
          "criteria_disagree": null,
          "disagreement_note": null
        },
        "href": "https://www.cyclecalcs.com/full-moon-calendar.html"
      },
      {
        "kind": "new_moon",
        "label": "New Moon",
        "instant": "2026-08-12T18:37:11.343+01:00",
        "instant_precision_seconds": 1,
        "local_date": "2026-08-12",
        "days_from_query": 14.775826,
        "lunation_number": 1282,
        "distance_km": 366945.4005,
        "angular_diameter_arcsec": 1953.2378,
        "fraction_of_mean_distance": 0.954593,
        "apparent_size_vs_mean_percent": 4.756729,
        "apparent_brightness_vs_mean_percent": 9.739723,
        "illuminated_fraction": 0.000061,
        "apparent_magnitude": -4.03,
        "illumination_note": "At an exact quarter the illuminated fraction is about 0.5013 rather than 0.5000. The quarters are defined by the difference in ecliptic longitude, not by half illumination, and the two differ by a few hours of geometry. It is not rounded to look tidy.",
        "classification": {
          "applicable": true,
          "rule": "site",
          "rule_definition": "A supermoon is a phase occurring closer than 360,000 km and a micromoon further than 405,000 km, measured at the instant of the phase.",
          "is_supermoon": false,
          "is_micromoon": false,
          "supermoon_threshold_km": 360000,
          "micromoon_threshold_km": 405000,
          "espenak": {
            "rule_definition": "Espenak's rule, following Nolle: a new or full Moon qualifies when it is closer than ten percent of the way from the closest perigee to the farthest apogee of that CALENDAR YEAR. One threshold applies to the whole year, and it shifts by a few hundred kilometres from year to year as the extreme apsides move.",
            "qualifies": false,
            "threshold_km": 361617.591879,
            "threshold_year": 2026,
            "closest_perigee_of_year_km": 356641.002581,
            "farthest_apogee_of_year_km": 406406.895565
          },
          "criteria_disagree": false,
          "disagreement_note": null
        },
        "eclipse": {
          "type": "solar",
          "kind": "total",
          "peak": "2026-08-12T18:45:46.794+01:00",
          "minutes_from_phase_instant": 8.59085,
          "minutes_from_phase_instant_sign": "Positive means the eclipse peak FOLLOWS the phase instant; negative means it precedes it.",
          "note": "A new moon is a candidate for a solar eclipse and most are not one. The small offset between the two instants is the visible trace of how near the syzygy fell to a node."
        },
        "href": "https://www.cyclecalcs.com/full-moon-calendar.html"
      },
      {
        "kind": "first_quarter",
        "label": "First Quarter",
        "instant": "2026-08-20T03:46:58.247+01:00",
        "instant_precision_seconds": 1,
        "local_date": "2026-08-20",
        "days_from_query": 22.157619,
        "lunation_number": 1282,
        "distance_km": 401561.449948,
        "angular_diameter_arcsec": 1784.860536,
        "fraction_of_mean_distance": 1.044645,
        "apparent_size_vs_mean_percent": -4.27368,
        "apparent_brightness_vs_mean_percent": -8.364716,
        "illuminated_fraction": 0.501326,
        "apparent_magnitude": -10,
        "illumination_note": "At an exact quarter the illuminated fraction is about 0.5013 rather than 0.5000. The quarters are defined by the difference in ecliptic longitude, not by half illumination, and the two differ by a few hours of geometry. It is not rounded to look tidy.",
        "classification": {
          "applicable": false,
          "not_applicable_reason": "A supermoon or micromoon is by definition a new or full Moon. This is a first quarter, so neither rule applies to it. The distance is reported above either way.",
          "rule": null,
          "is_supermoon": null,
          "is_micromoon": null,
          "espenak": null,
          "criteria_disagree": null,
          "disagreement_note": null
        },
        "href": "https://www.cyclecalcs.com/full-moon-calendar.html"
      },
      "... 1 more row omitted for length ..."
    ],
    "summary": "The next full moon is on 2026-07-29, the Buck Moon. 5 phases fall in this window."
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/phases?end=2026-08-31&start=2026-07-29&tz=Europe%2FLondon",
    "docs": "https://www.cyclecalcs.com/api/reference.html#phases",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/full-moon-calendar.html",
    "explain": "https://www.cyclecalcs.com/learn/moon-phases.html",
    "related": [
      {
        "rel": "endpoint",
        "title": "The Moon right now",
        "href": "https://www.cyclecalcs.com/v2/moon"
      },
      {
        "rel": "endpoint",
        "title": "Perigee and apogee",
        "href": "https://www.cyclecalcs.com/v2/apsides"
      },
      {
        "rel": "page",
        "title": "Supermoons",
        "href": "https://www.cyclecalcs.com/supermoons.html"
      }
    ]
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#phases"
}

Some arrays are shortened above for length; the live response returns every row.

GET /v2/cycles Phase 2

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.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
stepnoduration: 1min, 1h, 6h, 1d, 7d, 1mo, 1yendpoint-specificThe stride of a range query, from 1 minute to 1 year. Only meaningful together with start.
countnointegerendpoint-specificHow many rows to return from start. Mutually exclusive with end.
keysnocomma-separated cycle keys(every key)Restrict the response to specific cycles by key, for example keys=synodic_month,tropical_year. The full list is at /v2/enums?set=cycle_keys.
kindnocomma-separated: computed_phase, observed, interval, state(no filter: every kind)Filters cycles by how their number is sourced: computed_phase from the ephemeris, observed from a real dataset (the sunspot index), interval a fixed length with no live phase, or state a discrete label rather than a fraction.
familynocomma-separated family names(no filter: every family)Filters cycles by their thematic grouping (for example lunar, solar, planetary). Family names are data-driven; see a /v2/cycles response for the current set.
includenocomma-separated: fraction, next_event, observed, definitionfraction, next_event, observedfraction is the 0-to-1 position in the cycle. next_event adds the next boundary instant. observed adds the raw observed value where the cycle is kind=observed. definition adds the prose definition of the cycle.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv, txt.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitnointegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.

Formats: json, csv, txt   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/cycles?at=2026-07-28T21:30:00Z
Example response (real, captured live)
{
  "endpoint": "/v2/cycles",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "at": "2026-07-28T21:30:00.000Z",
    "mode": "instant",
    "resolved_from": "at",
    "is_now": false,
    "latitude": null,
    "longitude": null,
    "elevation_m": 0,
    "tz": "UTC",
    "utc_offset_seconds": 0,
    "utc_offset_iso": "+00:00",
    "is_dst": false,
    "timezone_source": "default",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "keys": null,
    "kind": null,
    "family": null,
    "include": [
      "fraction",
      "next_event",
      "observed"
    ],
    "ignored": []
  },
  "data": {
    "note": "Positional astronomy only. A cycle here is a position in a repeating astronomical pattern and nothing else. These figures do not predict events on Earth, in markets, or in anyone's life, and no field in this response should be read that way. Where a cycle is measured from observations rather than computed from orbital mechanics, kind is observed and the position is an estimate. Where a cycle is a repeat interval with no live phase, fraction is null and the reason is stated rather than a number invented.",
    "instant": "2026-07-28T21:30:00.000Z",
    "decimal_year": 2026.5723173515983,
    "count": 21,
    "summary": "The Moon is full, day 14 of the synodic month. The Sun is 35 percent through the tropical year, in the northern summer quarter. Jupiter is 117.4 deg ahead of Saturn in heliocentric longitude. Solar Cycle 25 is roughly 60 percent through its nominal 11 years, which is an estimate rather than a measurement.",
    "cycles": [
      {
        "key": "synodic_month",
        "slug": "synodic-month",
        "name": "The synodic month",
        "family": "lunar_month",
        "kind": "computed_phase",
        "period_days": 29.530589,
        "period_text": "29.530589 days (29 d 12 h 44 min)",
        "fraction": 0.477828,
        "percent_complete": 47.782847,
        "fraction_method": "MoonPhase(t) divided by 360. MoonPhase returns the Moon's elongation from the Sun in ecliptic longitude, 0 at new moon and 180 at full.",
        "position_label": "Full Moon, day 14 of 29.53",
        "position_values": {
          "phase_angle_deg": 172.01825,
          "day_of_cycle": 14,
          "phase_code": "full_moon"
        },
        "next_event": {
          "kind": "full_moon",
          "label": "Full Moon",
          "instant": "2026-07-29T14:36:19.011Z",
          "days_until": 0.71272
        },
        "page_url": "https://www.cyclecalcs.com/cycles/synodic-month.html"
      },
      {
        "key": "sidereal_month",
        "slug": "sidereal-month",
        "name": "The sidereal month",
        "family": "lunar_month",
        "kind": "computed_phase",
        "period_days": 27.321661,
        "period_text": "27.321661 days (27 d 7 h 43 min)",
        "fraction": 0.826309,
        "percent_complete": 82.630926,
        "fraction_method": "The Moon's J2000 ecliptic longitude divided by 360. The J2000 ecliptic, not the ecliptic of date, because a sidereal month is measured against the stars and the equinox of date moves.",
        "position_label": "Moon at 297.4713 deg J2000 ecliptic longitude",
        "position_values": {
          "ecliptic_longitude_j2000_deg": 297.471333
        },
        "next_event": null,
        "page_url": "https://www.cyclecalcs.com/cycles/sidereal-month.html"
      },
      {
        "key": "anomalistic_month",
        "slug": "anomalistic-month",
        "name": "The anomalistic month",
        "family": "lunar_month",
        "kind": "computed_phase",
        "period_days": 27.55455,
        "period_text": "27.554550 days (27 d 13 h 19 min)",
        "fraction": 0.553122,
        "percent_complete": 55.312154,
        "fraction_method": "Elapsed time since the previous perigee divided by the interval to the next one, both from the engine's apsis search. The interval for this particular month is 28.1398 days, which is not the mean period and is not meant to be.",
        "position_label": "15.6 days past perigee, 12.6 days to the next",
        "position_values": {
          "previous_perigee": "2026-07-13T07:56:49.301Z",
          "next_perigee": "2026-08-10T11:18:04.529Z",
          "previous_perigee_distance_km": 359102.352973,
          "interval_days": 28.13976
        },
        "next_event": {
          "kind": "perigee",
          "label": "The Moon reaches perigee",
          "instant": "2026-08-10T11:18:04.529Z",
          "days_until": 12.575052
        },
        "page_url": "https://www.cyclecalcs.com/cycles/anomalistic-month.html"
      },
      {
        "key": "draconic_month",
        "slug": "draconic-month",
        "name": "The draconic month",
        "family": "lunar_month",
        "kind": "computed_phase",
        "period_days": 27.212221,
        "period_text": "27.212221 days (27 d 5 h 6 min)",
        "fraction": 0.904327,
        "percent_complete": 90.432696,
        "fraction_method": "Elapsed time since the previous ascending-node passage divided by the interval to the next one, both from the engine's true-node search. The interval for this particular month is 27.1685 days, which is not the mean period and is not meant to be.",
        "position_label": "24.6 days past the ascending node, 2.6 days from the next one",
        "position_values": {
          "previous_ascending_node": "2026-07-04T07:50:23.985Z",
          "next_ascending_node": "2026-07-31T11:52:58.543Z",
          "interval_days": 27.168456
        },
        "next_event": {
          "kind": "node_ascending",
          "label": "The Moon crosses the ecliptic going north",
          "instant": "2026-07-31T11:52:58.543Z",
          "days_until": 2.599289
        },
        "page_url": "https://www.cyclecalcs.com/cycles/draconic-month.html"
      },
      "... 17 more rows omitted for length ..."
    ]
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/cycles?at=2026-07-28T21%3A30%3A00Z",
    "docs": "https://www.cyclecalcs.com/api/reference.html#cycles",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/cycles.html",
    "explain": "https://www.cyclecalcs.com/cosmic-position.html",
    "related": [
      {
        "rel": "cycle",
        "title": "The synodic month",
        "href": "https://www.cyclecalcs.com/cycles/synodic-month.html"
      },
      {
        "rel": "cycle",
        "title": "The sidereal month",
        "href": "https://www.cyclecalcs.com/cycles/sidereal-month.html"
      },
      {
        "rel": "cycle",
        "title": "The anomalistic month",
        "href": "https://www.cyclecalcs.com/cycles/anomalistic-month.html"
      },
      {
        "rel": "cycle",
        "title": "The draconic month",
        "href": "https://www.cyclecalcs.com/cycles/draconic-month.html"
      }
    ]
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#cycles"
}

Some arrays are shortened above for length; the live response returns every row.

Planets

GET /v2/planet-board Phase 2

All eight planets in one request: place, brightness, apparent size, retrograde state, next station and whether it is worth looking tonight.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
stepnoduration: 1min, 1h, 6h, 1d, 7d, 1mo, 1yendpoint-specificThe stride of a range query, from 1 minute to 1 year. Only meaningful together with start.
countnointegerendpoint-specificHow many rows to return from start. Mutually exclusive with end.
bodiesnocomma-separated planet names, up to 20mercury,venus,mars,jupiter,saturn,uranus,neptune,plutoWhich planets to include. The Sun, Moon and Earth are not on the board; use /v2/positions for those.
bodynoa planet name (mercury to pluto)(see bodies)A single planet, to filter the board to one row. Mutually exclusive with bodies.
latnodecimal degrees, -90 to 90(none unless place resolves it)Latitude, north positive. Sent together with lon (or neither), unless place supplies both. Do not append N or S.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
day_anchornocivil | solar | utccivil if tz or place set a zone, else solarWhat midnight means when a day boundary is needed. civil: local midnight in the effective IANA zone, with the historical offset for that date. solar: local mean solar midnight from longitude alone, no timezone or DST. utc: UTC midnight.
includenocomma-separated: position, visibility, motion, rise_set, physical, heliocentricposition, visibility, motion, rise_setposition is place and distance. visibility is the plain-language verdict and elongation. motion is retrograde/direct state and rate. rise_set needs a location and adds rise/transit/set. physical adds magnitude and apparent diameter. heliocentric adds the Sun-centred position.
sortnodistance_from_sun | magnitude | elongation | altitudedistance_from_sunHow the returned rows are ordered. altitude requires a location.
refractionnonormal | nonenormalnormal applies the Saemundsson refraction model near the horizon; none reports the geometric, unrefracted altitude. Both are always published side by side in the response regardless of this setting.
framesnominimal | standard | allstandardHow many coordinate frames to publish per position. minimal: equatorial only. standard: adds ecliptic, and horizontal (alt/az) when a location is given. all: adds galactic, and horizontal always.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv, txt.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitnointegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.
cursornoopaque token(first page)Continues a paginated list from the previous response's links.next, copied unchanged. Stateless and unsigned; a malformed or foreign cursor returns 400 BAD_CURSOR.

Formats: json, csv, txt   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/planet-board?at=2026-07-28T21:30:00Z&lat=51.4778&lon=-0.0015&elevation_m=11&tz=Europe/London
Example response (real, captured live)
{
  "endpoint": "/v2/planet-board",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "at": "2026-07-28T21:30:00.000Z",
    "mode": "instant",
    "resolved_from": "at",
    "is_now": false,
    "latitude": 51.4778,
    "longitude": -0.0015,
    "elevation_m": 11,
    "tz": "Europe/London",
    "utc_offset_seconds": 3600,
    "utc_offset_iso": "+01:00",
    "is_dst": true,
    "timezone_source": "parameter",
    "day_anchor": "civil",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "bodies": [
      "mercury",
      "venus",
      "mars",
      "jupiter",
      "... 4 more rows omitted for length ..."
    ],
    "include": [
      "position",
      "visibility",
      "motion",
      "rise_set"
    ],
    "sort": "distance_from_sun",
    "frames": "standard",
    "refraction": "normal",
    "ignored": []
  },
  "data": {
    "instant": "2026-07-28T22:30:00.000+01:00",
    "count": 8,
    "summary": "Venus is in the evening sky after dusk. Mercury, Mars, Saturn and Uranus are in the morning sky before dawn. Jupiter is too close to the Sun to be seen. Saturn, Neptune and Pluto are moving retrograde.",
    "retrograde_now": [
      "Saturn",
      "Neptune",
      "Pluto"
    ],
    "observable_now": [],
    "above_horizon_now": [
      "Pluto"
    ],
    "bodies": [
      {
        "name": "Mercury",
        "code": "mercury",
        "kind": "planet",
        "constellation": {
          "name": "Gemini",
          "abbreviation": "Gem"
        },
        "tropical_sign": {
          "name": "Cancer",
          "code": "cancer",
          "sign_index": 3,
          "degree_in_sign": 17.512669,
          "degree_in_sign_whole": 17
        },
        "position": {
          "equatorial": {
            "j2000": {
              "ra_hours": 7.212124,
              "ra_string": "07h 12m 43.6s",
              "dec_deg": 19.413271,
              "dec_string": "+19 24 47.8"
            },
            "of_date": {
              "ra_hours": 7.238316,
              "ra_string": "07h 14m 17.9s",
              "dec_deg": 19.36835,
              "dec_string": "+19 22 06.1"
            }
          },
          "ecliptic": {
            "true_of_date": {
              "longitude_deg": 107.512669,
              "latitude_deg": -2.944578
            }
          },
          "horizontal": {
            "azimuth_deg": 340.74562,
            "compass": "NNW",
            "altitude_refracted_deg": -16.589157,
            "altitude_unrefracted_deg": -17.118637,
            "refraction_deg": 0.52948,
            "hour_angle_hours": 10.698977,
            "parallactic_angle_deg": 12.574145
          },
          "distance_au": 0.777742,
          "distance_km": 116348495.965749,
          "distance_light_minutes": 6.46828,
          "origin": "topocentric",
          "observer_height_m": 11,
          "distance_origin": "geocentric",
          "ecliptic_origin": "geocentric",
          "origin_note": "Directions here are topocentric, measured from your coordinates. The ecliptic longitude and the distance are geocentric, measured from the centre of the Earth, because that is the frame tropical sign sectors are defined in and the frame planetary distances are published in. Do not combine the topocentric direction with the geocentric distance as one vector."
        },
        "physical": {
          "apparent_magnitude": 0.86,
          "angular_diameter_arcsec": 8.650292,
          "phase_angle_deg": 119.788195,
          "illuminated_fraction": 0.251602,
          "illuminated_percent": 25.160242,
          "ring_tilt_deg": null
        },
        "visibility": {
          "elongation_deg": 18.536022,
          "sun_side": "morning",
          "ecliptic_separation_deg": 18.309042,
          "code": "morning_sky",
          "phrase": "Morning sky, before dawn",
          "naked_eye": true,
          "is_up": false,
          "verdict": "Mercury is 18.5 deg west of the Sun and rises about 77 minutes before it, in the morning sky.",
          "sun_is_up": false,
          "observable_now": false
        },
        "motion": {
          "apparent_motion": "direct",
          "longitude_rate_deg_per_day": 0.489668,
          "is_retrograde": false,
          "stationary_band_deg_per_day": 0.0005,
          "search_horizon_days": 400,
          "method": "central difference of apparent geocentric ecliptic longitude over plus and minus 0.02 days, refined to 1 second",
          "next_station": {
            "kind": "station_retrograde",
            "label": "Mercury stationary, beginning retrograde motion",
            "instant": "2026-10-24T08:15:20.973+01:00",
            "instant_precision_seconds": 2,
            "days_from_query": 87.406493
          }
        },
        "rise_set": {
          "status": "normal",
          "status_label": "Rises and sets",
          "special": null,
          "events": [
            {
              "kind": "rise",
              "instant": "2026-07-28T04:01:19.564+01:00",
              "azimuth_deg": 57.239414,
              "altitude_deg": -0.566069
            },
            {
              "kind": "upper_transit",
              "instant": "2026-07-28T11:48:53.181+01:00",
              "azimuth_deg": 179.999443,
              "altitude_deg": 57.819927
            },
            {
              "kind": "set",
              "instant": "2026-07-28T19:37:16.059+01:00",
              "azimuth_deg": 302.979898,
              "altitude_deg": -0.566066
            },
            {
              "kind": "lower_transit",
              "instant": "2026-07-28T23:47:58.191+01:00",
              "azimuth_deg": 359.999998,
              "altitude_deg": -18.629193
            }
          ],
          "rise": [
            "2026-07-28T04:01:19.564+01:00"
          ],
          "set": [
            "2026-07-28T19:37:16.059+01:00"
          ],
          "transit": [
            "2026-07-28T11:48:53.181+01:00"
          ],
          "above_horizon_minutes": 935.9,
          "window": {
            "start": "2026-07-28T00:00:00.000+01:00",
            "end": "2026-07-29T00:00:00.000+01:00",
            "anchor": "civil",
            "length_minutes": 1440
          }
        },
        "href": "https://www.cyclecalcs.com/planets/mercury.html"
      },
      {
        "name": "Venus",
        "code": "venus",
        "kind": "planet",
        "constellation": {
          "name": "Leo",
          "abbreviation": "Leo"
        },
        "tropical_sign": {
          "name": "Virgo",
          "code": "virgo",
          "sign_index": 5,
          "degree_in_sign": 20.815222,
          "degree_in_sign_whole": 20
        },
        "position": {
          "equatorial": {
            "j2000": {
              "ra_hours": 11.420636,
              "ra_string": "11h 25m 14.3s",
              "dec_deg": 4.0046,
              "dec_string": "+04 00 16.6"
            },
            "of_date": {
              "ra_hours": 11.443606,
              "ra_string": "11h 26m 37.0s",
              "dec_deg": 3.85758,
              "dec_string": "+03 51 27.3"
            }
          },
          "ecliptic": {
            "true_of_date": {
              "longitude_deg": 170.815222,
              "latitude_deg": 0.239058
            }
          },
          "horizontal": {
            "azimuth_deg": 278.196223,
            "compass": "W",
            "altitude_refracted_deg": -0.930928,
            "altitude_unrefracted_deg": -1.573343,
            "refraction_deg": 0.642415,
            "hour_angle_hours": 6.493687,
            "parallactic_angle_deg": 38.159734
          },
          "distance_au": 0.82502,
          "distance_km": 123421285.418597,
          "distance_light_minutes": 6.861485,
          "origin": "topocentric",
          "observer_height_m": 11,
          "distance_origin": "geocentric",
          "ecliptic_origin": "geocentric",
          "origin_note": "Directions here are topocentric, measured from your coordinates. The ecliptic longitude and the distance are geocentric, measured from the centre of the Earth, because that is the frame tropical sign sectors are defined in and the frame planetary distances are published in. Do not combine the topocentric direction with the geocentric distance as one vector."
        },
        "physical": {
          "apparent_magnitude": -4.29,
          "angular_diameter_arcsec": 20.227846,
          "phase_angle_deg": 81.52526,
          "illuminated_fraction": 0.573687,
          "illuminated_percent": 57.368669,
          "ring_tilt_deg": null
        },
        "visibility": {
          "elongation_deg": 44.994297,
          "sun_side": "evening",
          "ecliptic_separation_deg": 44.992183,
          "code": "evening_sky",
          "phrase": "Evening sky, after dusk",
          "naked_eye": true,
          "is_up": false,
          "verdict": "Venus is 45.0 deg east of the Sun and sets about 89 minutes after it, in the evening sky.",
          "sun_is_up": false,
          "observable_now": false
        },
        "motion": {
          "apparent_motion": "direct",
          "longitude_rate_deg_per_day": 1.05161,
          "is_retrograde": false,
          "stationary_band_deg_per_day": 0.0005,
          "search_horizon_days": 643,
          "method": "central difference of apparent geocentric ecliptic longitude over plus and minus 0.02 days, refined to 1 second",
          "next_station": {
            "kind": "station_retrograde",
            "label": "Venus stationary, beginning retrograde motion",
            "instant": "2026-10-03T08:10:06.584+01:00",
            "instant_precision_seconds": 2,
            "days_from_query": 66.402854
          }
        },
        "rise_set": {
          "status": "normal",
          "status_label": "Rises and sets",
          "special": null,
          "events": [
            {
              "kind": "lower_transit",
              "instant": "2026-07-28T04:00:31.718+01:00",
              "azimuth_deg": 359.999997,
              "altitude_deg": -33.884511
            },
            {
              "kind": "rise",
              "instant": "2026-07-28T09:36:04.069+01:00",
              "azimuth_deg": 82.659795,
              "altitude_deg": -0.56607
            },
            {
              "kind": "upper_transit",
              "instant": "2026-07-28T16:00:26.259+01:00",
              "azimuth_deg": 179.999993,
              "altitude_deg": 42.530239
            },
            {
              "kind": "set",
              "instant": "2026-07-28T22:23:29.167+01:00",
              "azimuth_deg": 276.920838,
              "altitude_deg": -0.566068
            }
          ],
          "rise": [
            "2026-07-28T09:36:04.069+01:00"
          ],
          "set": [
            "2026-07-28T22:23:29.167+01:00"
          ],
          "transit": [
            "2026-07-28T16:00:26.259+01:00"
          ],
          "above_horizon_minutes": 767.4,
          "window": {
            "start": "2026-07-28T00:00:00.000+01:00",
            "end": "2026-07-29T00:00:00.000+01:00",
            "anchor": "civil",
            "length_minutes": 1440
          }
        },
        "href": "https://www.cyclecalcs.com/planets/venus.html"
      },
      {
        "name": "Mars",
        "code": "mars",
        "kind": "planet",
        "constellation": {
          "name": "Taurus",
          "abbreviation": "Tau"
        },
        "tropical_sign": {
          "name": "Gemini",
          "code": "gemini",
          "sign_index": 2,
          "degree_in_sign": 20.965181,
          "degree_in_sign_whole": 20
        },
        "position": {
          "equatorial": {
            "j2000": {
              "ra_hours": 5.317252,
              "ra_string": "05h 19m 02.1s",
              "dec_deg": 23.151784,
              "dec_string": "+23 09 06.4"
            },
            "of_date": {
              "ra_hours": 5.344285,
              "ra_string": "05h 20m 39.4s",
              "dec_deg": 23.179882,
              "dec_string": "+23 10 47.6"
            }
          },
          "ecliptic": {
            "true_of_date": {
              "longitude_deg": 80.965181,
              "latitude_deg": 0.050999
            }
          },
          "horizontal": {
            "azimuth_deg": 8.459641,
            "compass": "N",
            "altitude_refracted_deg": -14.388246,
            "altitude_unrefracted_deg": -14.933599,
            "refraction_deg": 0.545354,
            "hour_angle_hours": 12.593009,
            "parallactic_angle_deg": -5.720195
          },
          "distance_au": 2.010168,
          "distance_km": 300716879.816871,
          "distance_light_minutes": 16.718059,
          "origin": "topocentric",
          "observer_height_m": 11,
          "distance_origin": "geocentric",
          "ecliptic_origin": "geocentric",
          "origin_note": "Directions here are topocentric, measured from your coordinates. The ecliptic longitude and the distance are geocentric, measured from the centre of the Earth, because that is the frame tropical sign sectors are defined in and the frame planetary distances are published in. Do not combine the topocentric direction with the geocentric distance as one vector."
        },
        "physical": {
          "apparent_magnitude": 1.31,
          "angular_diameter_arcsec": 4.658977,
          "phase_angle_deg": 29.029036,
          "illuminated_fraction": 0.937187,
          "illuminated_percent": 93.718695,
          "ring_tilt_deg": null
        },
        "visibility": {
          "elongation_deg": 44.856264,
          "sun_side": "morning",
          "ecliptic_separation_deg": 44.857898,
          "code": "morning_sky",
          "phrase": "Morning sky, before dawn",
          "naked_eye": true,
          "is_up": false,
          "verdict": "Mars is 44.9 deg west of the Sun and rises about 217 minutes before it, in the morning sky.",
          "sun_is_up": false,
          "observable_now": false
        },
        "motion": {
          "apparent_motion": "direct",
          "longitude_rate_deg_per_day": 0.679367,
          "is_retrograde": false,
          "stationary_band_deg_per_day": 0.0005,
          "search_horizon_days": 858,
          "method": "central difference of apparent geocentric ecliptic longitude over plus and minus 0.02 days, refined to 1 second",
          "next_station": {
            "kind": "station_retrograde",
            "label": "Mars stationary, beginning retrograde motion",
            "instant": "2027-01-10T13:01:12.064+00:00",
            "instant_precision_seconds": 2,
            "days_from_query": 165.646667
          }
        },
        "rise_set": {
          "status": "normal",
          "status_label": "Rises and sets",
          "special": null,
          "events": [
            {
              "kind": "rise",
              "instant": "2026-07-28T01:40:47.150+01:00",
              "azimuth_deg": 49.978131,
              "altitude_deg": -0.566069
            },
            {
              "kind": "upper_transit",
              "instant": "2026-07-28T09:54:56.492+01:00",
              "azimuth_deg": 179.999991,
              "altitude_deg": 61.680128
            },
            {
              "kind": "set",
              "instant": "2026-07-28T18:09:23.916+01:00",
              "azimuth_deg": 310.10301,
              "altitude_deg": -0.566067
            },
            {
              "kind": "lower_transit",
              "instant": "2026-07-28T21:54:26.614+01:00",
              "azimuth_deg": 359.999995,
              "altitude_deg": -14.801417
            }
          ],
          "rise": [
            "2026-07-28T01:40:47.150+01:00"
          ],
          "set": [
            "2026-07-28T18:09:23.916+01:00"
          ],
          "transit": [
            "2026-07-28T09:54:56.492+01:00"
          ],
          "above_horizon_minutes": 988.6,
          "window": {
            "start": "2026-07-28T00:00:00.000+01:00",
            "end": "2026-07-29T00:00:00.000+01:00",
            "anchor": "civil",
            "length_minutes": 1440
          }
        },
        "href": "https://www.cyclecalcs.com/planets/mars.html"
      },
      {
        "name": "Jupiter",
        "code": "jupiter",
        "kind": "planet",
        "constellation": {
          "name": "Cancer",
          "abbreviation": "Cnc"
        },
        "tropical_sign": {
          "name": "Leo",
          "code": "leo",
          "sign_index": 4,
          "degree_in_sign": 6.273803,
          "degree_in_sign_whole": 6
        },
        "position": {
          "equatorial": {
            "j2000": {
              "ra_hours": 8.559682,
              "ra_string": "08h 33m 34.9s",
              "dec_deg": 19.251789,
              "dec_string": "+19 15 06.4"
            },
            "of_date": {
              "ra_hours": 8.585277,
              "ra_string": "08h 35m 07.0s",
              "dec_deg": 19.160558,
              "dec_string": "+19 09 38.0"
            }
          },
          "ecliptic": {
            "true_of_date": {
              "longitude_deg": 126.273803,
              "latitude_deg": 0.472265
            }
          },
          "horizontal": {
            "azimuth_deg": 322.008936,
            "compass": "NW",
            "altitude_refracted_deg": -10.715779,
            "altitude_unrefracted_deg": -11.287621,
            "refraction_deg": 0.571842,
            "hour_angle_hours": 9.352017,
            "parallactic_angle_deg": 23.9445
          },
          "distance_au": 6.301024,
          "distance_km": 942619701.995286,
          "distance_light_minutes": 52.404015,
          "origin": "topocentric",
          "observer_height_m": 11,
          "distance_origin": "geocentric",
          "ecliptic_origin": "geocentric",
          "origin_note": "Directions here are topocentric, measured from your coordinates. The ecliptic longitude and the distance are geocentric, measured from the centre of the Earth, because that is the frame tropical sign sectors are defined in and the frame planetary distances are published in. Do not combine the topocentric direction with the geocentric distance as one vector."
        },
        "physical": {
          "apparent_magnitude": -1.79,
          "angular_diameter_arcsec": 31.287875,
          "phase_angle_deg": 0.125942,
          "illuminated_fraction": 0.999999,
          "illuminated_percent": 99.999879,
          "ring_tilt_deg": null
        },
        "visibility": {
          "elongation_deg": 0.653936,
          "sun_side": "evening",
          "ecliptic_separation_deg": 0.45238,
          "code": "glare",
          "phrase": "Lost in the Sun's glare",
          "naked_eye": true,
          "is_up": false,
          "verdict": "Jupiter is only 0.7 deg from the Sun and cannot be seen.",
          "sun_is_up": false,
          "observable_now": false
        },
        "motion": {
          "apparent_motion": "direct",
          "longitude_rate_deg_per_day": 0.221485,
          "is_retrograde": false,
          "stationary_band_deg_per_day": 0.0005,
          "search_horizon_days": 439,
          "method": "central difference of apparent geocentric ecliptic longitude over plus and minus 0.02 days, refined to 1 second",
          "next_station": {
            "kind": "station_retrograde",
            "label": "Jupiter stationary, beginning retrograde motion",
            "instant": "2026-12-13T00:58:53.785+00:00",
            "instant_precision_seconds": 2,
            "days_from_query": 137.145067
          }
        },
        "rise_set": {
          "status": "normal",
          "status_label": "Rises and sets",
          "special": null,
          "events": [
            {
              "kind": "lower_transit",
              "instant": "2026-07-28T01:11:34.319+01:00",
              "azimuth_deg": 359.99999,
              "altitude_deg": -18.800446
            },
            {
              "kind": "rise",
              "instant": "2026-07-28T05:22:59.108+01:00",
              "azimuth_deg": 57.286447,
              "altitude_deg": -0.566124
            },
            {
              "kind": "upper_transit",
              "instant": "2026-07-28T13:10:03.517+01:00",
              "azimuth_deg": 179.999855,
              "altitude_deg": 57.714486
            },
            {
              "kind": "set",
              "instant": "2026-07-28T20:56:54.695+01:00",
              "azimuth_deg": 302.65069,
              "altitude_deg": -0.56607
            }
          ],
          "rise": [
            "2026-07-28T05:22:59.108+01:00"
          ],
          "set": [
            "2026-07-28T20:56:54.695+01:00"
          ],
          "transit": [
            "2026-07-28T13:10:03.517+01:00"
          ],
          "above_horizon_minutes": 933.9,
          "window": {
            "start": "2026-07-28T00:00:00.000+01:00",
            "end": "2026-07-29T00:00:00.000+01:00",
            "anchor": "civil",
            "length_minutes": 1440
          }
        },
        "href": "https://www.cyclecalcs.com/planets/jupiter.html"
      },
      "... 4 more rows omitted for length ..."
    ]
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/planet-board?at=2026-07-28T21%3A30%3A00Z&elevation_m=11&lat=51.4778&lon=-0.0015&tz=Europe%2FLondon",
    "docs": "https://www.cyclecalcs.com/api/reference.html#planet-board",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/planets/",
    "related": [
      {
        "rel": "endpoint",
        "title": "Where every body is, in every frame",
        "href": "https://www.cyclecalcs.com/v2/positions"
      },
      {
        "rel": "endpoint",
        "title": "Every retrograde and direct station",
        "href": "https://www.cyclecalcs.com/v2/retrogrades"
      }
    ]
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#planet-board"
}

Some arrays are shortened above for length; the live response returns every row.

GET /v2/retrogrades Phase 2

Every retrograde and direct station of every planet in a window, with the loop length, its arc, and the interval of triple crossing.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
bodynoa planet name (mercury to pluto)(see bodies)A single planet. Mutually exclusive with bodies. A fixed radec: target has no retrograde motion and is refused.
bodiesnocomma-separated planet names, up to 20mercury,venus,mars,jupiter,saturn,uranus,neptune,plutoWhich planets to scan for stations.
motionnoretrograde | direct | allallFilters the returned stations to retrograde (direct-to-retrograde) turns, direct turns, or both.
includenocomma-separated: periods, stations, triple_crossing, companion_event, rate_curveperiods, stationsperiods lists each retrograde loop with its start/end and arc. stations adds the individual station instants. triple_crossing adds the interval where the planet crosses its own pre-loop longitude three times. companion_event links each loop to the nearby opposition or conjunction that causes it. rate_curve adds a sampled longitude-rate series (see step).
stepnoduration, 1h to 30d1dSample stride for include=rate_curve only; it has no effect on the station search itself, which uses a fixed internal stride per body.
latnodecimal degrees, -90 to 90(none unless place resolves it)Latitude, north positive. Sent together with lon (or neither), unless place supplies both. Do not append N or S.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitnointegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.
cursornoopaque token(first page)Continues a paginated list from the previous response's links.next, copied unchanged. Stateless and unsigned; a malformed or foreign cursor returns 400 BAD_CURSOR.

Formats: json, csv   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/retrogrades?body=mercury&start=2026-09-01&end=2026-12-31&include=periods,stations,triple_crossing,companion_event
Example response (real, captured live)
{
  "endpoint": "/v2/retrogrades",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "start": "2026-09-01T00:00:00.000Z",
    "end": "2026-12-31T00:00:00.000Z",
    "mode": "range",
    "resolved_from": "start",
    "latitude": null,
    "longitude": null,
    "elevation_m": 0,
    "tz": "UTC",
    "utc_offset_seconds": 0,
    "utc_offset_iso": "+00:00",
    "is_dst": false,
    "timezone_source": "default",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "bodies": [
      "mercury"
    ],
    "motion": "all",
    "include": [
      "periods",
      "stations",
      "triple_crossing",
      "companion_event"
    ],
    "step": "1d",
    "ignored": []
  },
  "data": {
    "window": {
      "start": "2026-09-01T00:00:00.000Z",
      "end": "2026-12-31T00:00:00.000Z",
      "measured_from": "start",
      "length_days": 121
    },
    "definition": "A body is retrograde when its apparent geocentric ecliptic longitude of date is decreasing. A station is the instant at which that rate passes through zero. This is an appearance produced by the relative motion of the Earth and the body; no body reverses its orbit.",
    "body_count": 1,
    "station_count": 2,
    "period_count": 1,
    "bodies": [
      {
        "name": "Mercury",
        "code": "mercury",
        "synodic_period_days": 115.88,
        "retrograde_periods_per_year_typical": 3.2,
        "periods": [
          {
            "station_retrograde": {
              "kind": "station_retrograde",
              "label": "Mercury stationary, beginning retrograde motion",
              "instant": "2026-10-24T07:15:20.973Z",
              "instant_precision_seconds": 2,
              "body": "Mercury",
              "value_deg": 230.979608,
              "value_unit": "deg",
              "days_from_query": 53.302326,
              "ecliptic_longitude_deg": 230.979608,
              "ecliptic_latitude_deg": -3.005705,
              "constellation": "Libra",
              "elongation_from_sun_deg": 20.297624,
              "elongation_direction": "evening"
            },
            "station_direct": {
              "kind": "station_direct",
              "label": "Mercury stationary, resuming direct motion",
              "instant": "2026-11-13T15:52:11.950Z",
              "instant_precision_seconds": 2,
              "body": "Mercury",
              "value_deg": 215.033898,
              "value_unit": "deg",
              "days_from_query": 73.661249,
              "ecliptic_longitude_deg": 215.033898,
              "ecliptic_latitude_deg": 2.05076,
              "constellation": "Virgo",
              "elongation_from_sun_deg": 16.37399,
              "elongation_direction": "morning"
            },
            "duration_days": 20.358923,
            "duration_text": "20 days 8 h 37 m",
            "retrograde_arc_deg": 15.94571,
            "mean_retrograde_rate_deg_per_day": -0.78323,
            "peak_retrograde_rate_deg_per_day": -1.308946,
            "peak_retrograde_rate_at": "2026-11-04T07:28:00.000Z",
            "peak_retrograde_rate_precision_seconds": 300,
            "midpoint_instant": "2026-11-03T11:33:46.462Z",
            "constellations_crossed": [
              "Libra",
              "Virgo"
            ],
            "triple_crossing_window": {
              "start": "2026-10-04T09:10:52.101Z",
              "end": "2026-11-30T06:16:56.940Z",
              "start_kind": "direct_crossing_of_station_direct_longitude",
              "end_kind": "direct_crossing_of_station_retrograde_longitude",
              "longitude_range_deg": {
                "from_deg": 215.033898,
                "to_deg": 230.979608,
                "width_deg": 15.94571,
                "crosses_zero": false
              },
              "length_days": 56.879223,
              "start_resolved": true,
              "end_resolved": true,
              "search_horizon_days": 180,
              "explanation": "Between these two instants Mercury occupies the 15.9457 deg of ecliptic longitude running forward from 215.0339 deg to 230.9796 deg, and it passes through every longitude in that band three times: once moving forward, once moving backward, and once moving forward again. The window opens when the body first reaches the longitude of its later direct station and closes when it returns to the longitude of its earlier retrograde station. Some traditions have a name for this interval; here it is only the geometry of a triple crossing."
            },
            "companion_event": {
              "kind": "conjunction",
              "label": "Mercury at inferior conjunction with the Sun",
              "instant": "2026-11-04T14:18:36.902Z",
              "longitude_convention": "heliocentric_relative_longitude",
              "convention_note": "This instant is the classical heliocentric definition, when the body and the Earth reach the stated relative longitude. Every other instant on this endpoint is apparent geocentric longitude of date. The two differ by minutes, and /v2/conjunctions uses the geocentric definition, so the same event can carry two instants a few minutes apart under two named conventions.",
              "instant_precision_seconds": 60,
              "body": "Mercury",
              "value_deg": null,
              "value_unit": null,
              "days_from_query": 64.59626,
              "relation": "inferior_conjunction",
              "inside_retrograde_period": true,
              "note": "An inner planet's retrograde loop always brackets its inferior conjunction, and an outer planet's always brackets its opposition. That is the geometry, not a coincidence."
            }
          }
        ],
        "stations": [
          {
            "kind": "station_retrograde",
            "label": "Mercury stationary, beginning retrograde motion",
            "instant": "2026-10-24T07:15:20.973Z",
            "instant_precision_seconds": 2,
            "body": "Mercury",
            "value_deg": 230.979608,
            "value_unit": "deg",
            "days_from_query": 53.302326,
            "ecliptic_longitude_deg": 230.979608,
            "ecliptic_latitude_deg": -3.005705,
            "constellation": "Libra",
            "elongation_from_sun_deg": 20.297624,
            "elongation_direction": "evening"
          },
          {
            "kind": "station_direct",
            "label": "Mercury stationary, resuming direct motion",
            "instant": "2026-11-13T15:52:11.950Z",
            "instant_precision_seconds": 2,
            "body": "Mercury",
            "value_deg": 215.033898,
            "value_unit": "deg",
            "days_from_query": 73.661249,
            "ecliptic_longitude_deg": 215.033898,
            "ecliptic_latitude_deg": 2.05076,
            "constellation": "Virgo",
            "elongation_from_sun_deg": 16.37399,
            "elongation_direction": "morning"
          }
        ]
      }
    ],
    "instant_state": null
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/retrogrades?body=mercury&end=2026-12-31&include=periods%2Cstations%2Ctriple_crossing%2Ccompanion_event&start=2026-09-01",
    "docs": "https://www.cyclecalcs.com/api/reference.html#retrogrades",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/retrograde.html",
    "explain": "https://www.cyclecalcs.com/learn/retrograde.html",
    "related": [
      {
        "rel": "body",
        "title": "Mercury",
        "href": "https://www.cyclecalcs.com/planets/mercury.html"
      }
    ]
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#retrogrades"
}
GET /v2/conjunctions Phase 2

Close approaches between body pairs in a window, ranked, with the separation and whether the pair is observable or lost in twilight.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
bodiesnocomma-separated body names, up to 20sun,moon,mercury,venus,mars,jupiter,saturnThe pool of bodies to search for close approaches; every pair among them is checked. Mutually exclusive with pairs.
pairsnocomma-separated pairs, each two bodies joined by a hyphen(none: bodies generates the pairs)An explicit list of pairs to check instead of every combination from bodies, for example pairs=jupiter-saturn,venus-mars.
max_separation_degnodegrees, 0 to 1805Only minima at or under this separation are returned. Filters the result; it never narrows the search itself, so lowering it cannot hide or invent a minimum.
min_elongation_degnodegrees, 0 to 1800Excludes a close approach whose solar elongation is under this figure, that is, one that happens too near the Sun's glare to observe.
kindnoconjunction | close_approach | allallconjunction: the pair shares the same ecliptic longitude at the minimum (a true conjunction). close_approach: the minimum separation occurs without that (for example on the far side, near opposition). all returns both.
framenoapparent | geocentric | eclipticapparentThe frame the separation and conjunction test are measured in.
stepnoduration(an internal search stride)Sample stride for the minimum-finding search.
sortnotime | separationtimeOrder of the returned rows: chronological, or closest approach first.
latnodecimal degrees, -90 to 90(none unless place resolves it)Latitude, north positive. Sent together with lon (or neither), unless place supplies both. Do not append N or S.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitnointegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.
cursornoopaque token(first page)Continues a paginated list from the previous response's links.next, copied unchanged. Stateless and unsigned; a malformed or foreign cursor returns 400 BAD_CURSOR.

Formats: json, csv   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/conjunctions?bodies=venus,jupiter,mars&start=2026-01-01&end=2027-01-01&max_separation_deg=20
Example response (real, captured live)
{
  "endpoint": "/v2/conjunctions",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "start": "2026-01-01T00:00:00.000Z",
    "end": "2027-01-01T00:00:00.000Z",
    "mode": "range",
    "resolved_from": "start",
    "latitude": null,
    "longitude": null,
    "elevation_m": 0,
    "tz": "UTC",
    "utc_offset_seconds": 0,
    "utc_offset_iso": "+00:00",
    "is_dst": false,
    "timezone_source": "default",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "bodies": [
      "Venus",
      "Jupiter",
      "Mars"
    ],
    "pairs": null,
    "max_separation_deg": 20,
    "min_elongation_deg": 0,
    "kind": "all",
    "frame": "apparent",
    "step": "6h",
    "sort": "time",
    "ignored": []
  },
  "data": {
    "window": {
      "start": "2026-01-01T00:00:00.000Z",
      "end": "2027-01-01T00:00:00.000Z",
      "measured_from": "start",
      "length_days": 365
    },
    "pairs_searched": [
      {
        "a": "Venus",
        "b": "Jupiter"
      },
      {
        "a": "Venus",
        "b": "Mars"
      },
      {
        "a": "Jupiter",
        "b": "Mars"
      }
    ],
    "pair_count": 3,
    "origin": "geocentric",
    "frame": "apparent",
    "approach_count": 3,
    "filtered_out_count": 0,
    "scan": {
      "stride": "6h",
      "samples_evaluated": 4386,
      "refinement": "golden section to a one-second bracket",
      "padded_by": "one stride beyond each end of the window, so an approach at the very edge is still found; minima falling outside the window are discarded",
      "note": "Two minima closer together than the stride may be reported as one. Reduce step to separate them."
    },
    "approaches": [
      {
        "kind": "conjunction",
        "label": "Venus and Mars in conjunction, 0.17 deg apart",
        "instant": "2026-01-08T03:11:51.286Z",
        "instant_precision_seconds": 1,
        "body": null,
        "value_deg": 0.173518,
        "value_unit": "deg",
        "days_from_query": 7.133232,
        "pair": {
          "a": "Venus",
          "b": "Mars"
        },
        "separation_deg": 0.173518,
        "separation_arcmin": 10.411086,
        "position_angle_of_b_from_a_deg": 175.57635,
        "ecliptic_longitude_difference_deg": -0.010058,
        "ecliptic_latitude_difference_deg": -0.173226,
        "longitude_conjunction_instant": "2026-01-08T02:42:06.718Z",
        "minimum_offset_from_longitude_conjunction_minutes": 29.742797,
        "elongation_from_sun_deg": 0.835117,
        "elongation_direction": "evening",
        "observability": "glare",
        "observability_label": "Lost in the Sun's glare",
        "magnitudes": {
          "a": -4,
          "b": 1.2
        },
        "combined_apparent_note": "At 10.4 arcmin the pair is close, but it sits only 0.8 deg from the Sun and is lost in the glare. Sweeping for it with a telescope or binocular risks putting the Sun in the field, so it is not an observing target.",
        "href": "https://www.cyclecalcs.com/conjunctions/venus-mars.html"
      },
      {
        "kind": "conjunction",
        "label": "Venus and Jupiter in conjunction, 1.61 deg apart",
        "instant": "2026-06-09T19:47:32.457Z",
        "instant_precision_seconds": 1,
        "body": null,
        "value_deg": 1.608214,
        "value_unit": "deg",
        "days_from_query": 159.824681,
        "pair": {
          "a": "Venus",
          "b": "Jupiter"
        },
        "separation_deg": 1.608214,
        "separation_arcmin": 96.492833,
        "position_angle_of_b_from_a_deg": 190.562163,
        "ecliptic_longitude_difference_deg": 0.007403,
        "ecliptic_latitude_difference_deg": -1.608197,
        "longitude_conjunction_instant": "2026-06-09T19:58:30.484Z",
        "minimum_offset_from_longitude_conjunction_minutes": -10.967125,
        "elongation_from_sun_deg": 36.830544,
        "elongation_direction": "evening",
        "observability": "evening_sky",
        "observability_label": "Evening sky, after dusk",
        "magnitudes": {
          "a": -4,
          "b": -1.9
        },
        "combined_apparent_note": "At 96.5 arcmin the pair is a close naked-eye pairing and fits in most binoculars.",
        "href": "https://www.cyclecalcs.com/conjunctions/venus-jupiter.html"
      },
      {
        "kind": "conjunction",
        "label": "Jupiter and Mars in conjunction, 1.19 deg apart",
        "instant": "2026-11-16T02:03:05.884Z",
        "instant_precision_seconds": 1,
        "body": null,
        "value_deg": 1.193722,
        "value_unit": "deg",
        "days_from_query": 319.085485,
        "pair": {
          "a": "Jupiter",
          "b": "Mars"
        },
        "separation_deg": 1.193722,
        "separation_arcmin": 71.623343,
        "position_angle_of_b_from_a_deg": 16.66615,
        "ecliptic_longitude_difference_deg": -0.065308,
        "ecliptic_latitude_difference_deg": 1.191936,
        "longitude_conjunction_instant": "2026-11-16T06:21:59.098Z",
        "minimum_offset_from_longitude_conjunction_minutes": -258.886894,
        "elongation_from_sun_deg": 87.850401,
        "elongation_direction": "morning",
        "observability": "morning_sky",
        "observability_label": "Morning sky, before dawn",
        "magnitudes": {
          "a": -2.1,
          "b": 0.7
        },
        "combined_apparent_note": "At 71.6 arcmin the pair is a close naked-eye pairing and fits in most binoculars.",
        "href": "https://www.cyclecalcs.com/conjunctions/mars-jupiter.html"
      }
    ]
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/conjunctions?bodies=venus%2Cjupiter%2Cmars&end=2027-01-01&max_separation_deg=20&start=2026-01-01",
    "docs": "https://www.cyclecalcs.com/api/reference.html#conjunctions",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/conjunctions/",
    "related": [
      {
        "rel": "endpoint",
        "title": "Separation between Venus and Jupiter across this window",
        "href": "https://www.cyclecalcs.com/v2/separation?a=venus&b=jupiter&start=2026-01-01&end=2027-01-01"
      },
      {
        "rel": "endpoint",
        "title": "All eight planets at one instant",
        "href": "https://www.cyclecalcs.com/v2/planet-board"
      }
    ]
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#conjunctions"
}
GET /v2/separation Phase 2

The angular separation between any two targets at an instant, and the minima of that separation across a window.

ParameterRequiredTypeDefaultDescription
ayesa body name, or radec:<ra>,<dec>[,j2000](required)The first target. Required; must differ from b.
byesa body name, or radec:<ra>,<dec>[,j2000](required)The second target. Required; must differ from a.
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
stepnoduration: 1min, 1h, 6h, 1d, 7d, 1mo, 1yendpoint-specificThe stride of a range query, from 1 minute to 1 year. Only meaningful together with start.
max_separation_degnodegrees, 0 to 18010In a range query, only local minima at or under this separation are returned. Filters the result and never enters the search itself.
framenoapparent | geocentricapparentThe frame the separation is measured in.
latnodecimal degrees, -90 to 90(none unless place resolves it)Latitude, north positive. Sent together with lon (or neither), unless place supplies both. Do not append N or S.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv, txt.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitnointegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.
cursornoopaque token(first page)Continues a paginated list from the previous response's links.next, copied unchanged. Stateless and unsigned; a malformed or foreign cursor returns 400 BAD_CURSOR.

Formats: json, csv, txt   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/separation?a=jupiter&b=saturn&start=2020-01-01&end=2021-01-01&max_separation_deg=1
Example response (real, captured live)
{
  "endpoint": "/v2/separation",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "start": "2020-01-01T00:00:00.000Z",
    "end": "2021-01-01T00:00:00.000Z",
    "mode": "range",
    "resolved_from": "start",
    "latitude": null,
    "longitude": null,
    "elevation_m": 0,
    "tz": "UTC",
    "utc_offset_seconds": 0,
    "utc_offset_iso": "+00:00",
    "is_dst": false,
    "timezone_source": "default",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "a": "Jupiter",
    "b": "Saturn",
    "frame": "apparent",
    "max_separation_deg": 1,
    "step": "6h",
    "ignored": []
  },
  "data": {
    "pair": {
      "a": "Jupiter",
      "b": "Saturn"
    },
    "frame": "apparent",
    "origin": "geocentric",
    "definition": "Separation is the true angle between the two apparent directions.",
    "window": {
      "start": "2020-01-01T00:00:00.000Z",
      "end": "2021-01-01T00:00:00.000Z",
      "step": "6h",
      "measured_from": "start",
      "length_days": 366
    },
    "max_separation_deg": 1,
    "minimum_count": 1,
    "scan": {
      "stride": "6h",
      "samples_evaluated": 1466,
      "refinement": "golden section to a one-second bracket",
      "padded_by": "one stride beyond each end of the window, so a minimum at the very edge is still found; minima falling outside the window are discarded",
      "note": "Two minima closer together than the stride may be reported as one. Reduce step to separate them."
    },
    "minima": [
      {
        "kind": "conjunction",
        "instant": "2020-12-21T18:26:36.220Z",
        "instant_precision_seconds": 1,
        "separation_deg": 0.098793,
        "separation_arcmin": 5.92757,
        "separation_arcsec": 355.654222,
        "days_from_query": 355.768475,
        "longitude_conjunction_instant": "2020-12-21T18:26:14.399Z",
        "ecliptic_longitude_difference_deg": -0.000028,
        "ecliptic_latitude_difference_deg": 0.098793,
        "elongation_from_sun_deg": 30.133714,
        "elongation_direction": "evening",
        "elongation_refers_to": "Jupiter",
        "observability": "evening_sky",
        "observability_label": "Evening sky, after dusk",
        "a_position": {
          "ra_hours": 20.186481,
          "dec_deg": -20.513144,
          "distance_au": 5.925804
        },
        "b_position": {
          "ra_hours": 20.184965,
          "dec_deg": -20.416676,
          "distance_au": 10.827063
        },
        "position_angle_of_b_from_a_deg": 347.542114
      }
    ],
    "closest": {
      "kind": "conjunction",
      "instant": "2020-12-21T18:26:36.220Z",
      "instant_precision_seconds": 1,
      "separation_deg": 0.098793,
      "separation_arcmin": 5.92757,
      "separation_arcsec": 355.654222,
      "days_from_query": 355.768475,
      "longitude_conjunction_instant": "2020-12-21T18:26:14.399Z",
      "ecliptic_longitude_difference_deg": -0.000028,
      "ecliptic_latitude_difference_deg": 0.098793,
      "elongation_from_sun_deg": 30.133714,
      "elongation_direction": "evening",
      "elongation_refers_to": "Jupiter",
      "observability": "evening_sky",
      "observability_label": "Evening sky, after dusk",
      "a_position": {
        "ra_hours": 20.186481,
        "dec_deg": -20.513144,
        "distance_au": 5.925804
      },
      "b_position": {
        "ra_hours": 20.184965,
        "dec_deg": -20.416676,
        "distance_au": 10.827063
      },
      "position_angle_of_b_from_a_deg": 347.542114
    },
    "summary": "Jupiter and Saturn come closest on 2020-12-21, 0.099 deg apart."
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/separation?a=jupiter&b=saturn&end=2021-01-01&max_separation_deg=1&start=2020-01-01",
    "docs": "https://www.cyclecalcs.com/api/reference.html#separation",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/conjunctions/",
    "related": [
      {
        "rel": "endpoint",
        "title": "Close approaches across many pairs",
        "href": "https://www.cyclecalcs.com/v2/conjunctions"
      },
      {
        "rel": "endpoint",
        "title": "Where every body is, in every frame",
        "href": "https://www.cyclecalcs.com/v2/positions"
      }
    ]
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#separation"
}

Observing tools

GET /v2/twilight Phase 3

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.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe single day to compute when no range is sent; mutually exclusive with start. For a run of days, use start with end or count, one row per civil day, paged with limit and cursor.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
countnointegerendpoint-specificHow many rows to return from start. Mutually exclusive with end.
stepnoduration: 1min, 1h, 6h, 1d, 7d, 1mo, 1yendpoint-specificThe stride of a range query, from 1 minute to 1 year. Only meaningful together with start.
latyesdecimal degrees, -90 to 90(required)Required; every twilight boundary is defined relative to a horizon.
lonyesdecimal degrees, -180 to 180(required)Required alongside lat.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
day_anchornocivil | solar | utccivil if tz or place set a zone, else solarWhat midnight means when a day boundary is needed. civil: local midnight in the effective IANA zone, with the historical offset for that date. solar: local mean solar midnight from longitude alone, no timezone or DST. utc: UTC midnight.
depressionsnocomma-separated degrees, 0 to 30 (up to 8)(none: only the standard bands)Extra Sun-depression angles below the horizon to compute crossing times for, beyond the standard civil/nautical/astronomical bands, for example depressions=17,18.
refractionnonormal | nonenormalnormal applies the Saemundsson refraction model near the horizon; none reports the geometric, unrefracted altitude. Both are always published side by side in the response regardless of this setting.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
limitnointegerendpoint-specific page sizePage size for a paginated list response. Use together with cursor for later pages.
cursornoopaque token(first page)Continues a paginated list from the previous response's links.next, copied unchanged. Stateless and unsigned; a malformed or foreign cursor returns 400 BAD_CURSOR.

Formats: json, csv   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/twilight?lat=51.509&lon=-0.126&at=2026-11-14&tz=Europe/London
Example response (real, captured live)
{
  "endpoint": "/v2/twilight",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "at": "2026-11-14T00:00:00.000Z",
    "mode": "instant",
    "resolved_from": "at",
    "is_now": false,
    "latitude": 51.509,
    "longitude": -0.126,
    "elevation_m": 0,
    "tz": "Europe/London",
    "utc_offset_seconds": 0,
    "utc_offset_iso": "+00:00",
    "is_dst": false,
    "timezone_source": "parameter",
    "day_anchor": "civil",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "depressions": [],
    "refraction": "normal",
    "ignored": []
  },
  "data": {
    "window": {
      "start": "2026-11-14T00:00:00.000+00:00",
      "end": "2026-11-15T00:00:00.000+00:00",
      "anchor": "civil",
      "length_minutes": 1440
    },
    "status": "normal",
    "status_label": "Rises and sets",
    "special": null,
    "high_latitude_caution": false,
    "high_latitude_note": null,
    "intervals": [
      {
        "kind": "night",
        "label": "Night",
        "phase": "morning",
        "start": "2026-11-14T00:00:00.000+00:00",
        "end": "2026-11-14T05:19:57.922+00:00",
        "duration_minutes": 320,
        "start_reason": "window_start",
        "end_reason": "sun_left_depression",
        "open_start": true,
        "open_end": false
      },
      {
        "kind": "astronomical_twilight",
        "label": "Astronomical twilight",
        "phase": "morning",
        "start": "2026-11-14T05:19:57.922+00:00",
        "end": "2026-11-14T05:59:15.729+00:00",
        "duration_minutes": 39.3,
        "start_reason": "sun_left_depression",
        "end_reason": "sun_left_depression",
        "open_start": false,
        "open_end": false
      },
      {
        "kind": "nautical_twilight",
        "label": "Nautical twilight",
        "phase": "morning",
        "start": "2026-11-14T05:59:15.729+00:00",
        "end": "2026-11-14T06:39:54.291+00:00",
        "duration_minutes": 40.6,
        "start_reason": "sun_left_depression",
        "end_reason": "sun_left_depression",
        "open_start": false,
        "open_end": false
      },
      {
        "kind": "civil_twilight",
        "label": "Civil twilight",
        "phase": "morning",
        "start": "2026-11-14T06:39:54.291+00:00",
        "end": "2026-11-14T07:16:44.781+00:00",
        "duration_minutes": 36.8,
        "start_reason": "sun_left_depression",
        "end_reason": "sun_left_depression",
        "open_start": false,
        "open_end": false
      },
      "... 5 more rows omitted for length ..."
    ],
    "custom_intervals": [],
    "totals": {
      "day_minutes": 535.8,
      "civil_twilight_minutes": 73.6,
      "nautical_twilight_minutes": 81.2,
      "astronomical_twilight_minutes": 78.6,
      "night_minutes": 670.8,
      "checksum_minutes": 1440
    },
    "sun": {
      "transit": "2026-11-14T11:44:55.882+00:00",
      "transit_altitude_deg": 20.237692,
      "antitransit": "2026-11-14T23:45:00.689+00:00",
      "antitransit_altitude_deg": -56.677083
    },
    "definitions": {
      "sunrise_sunset_altitude_deg": -0.8333,
      "civil_altitude_deg": -6,
      "nautical_altitude_deg": -12,
      "astronomical_altitude_deg": -18,
      "note": "The three twilight angles are geometric definitions of the Sun's centre below the true horizon and carry no refraction term. The sunrise and sunset angle of -0.8333 deg is the conventional allowance for about 16 arcminutes of solar semidiameter plus about 34 arcminutes of horizon refraction."
    }
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/twilight?at=2026-11-14&lat=51.509&lon=-0.126&tz=Europe%2FLondon",
    "docs": "https://www.cyclecalcs.com/api/reference.html#twilight",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/sunrise-sunset-calculator.html"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#twilight"
}

Some arrays are shortened above for length; the live response returns every row.

GET /v2/dark-window Phase 3

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.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe first night to evaluate; nights counts forward from it. This endpoint has no range mode.
latyesdecimal degrees, -90 to 90(required)Required; the dark window is defined relative to a horizon and the local night.
lonyesdecimal degrees, -180 to 180(required)Required alongside lat.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
nightsnointeger, 1 to 621How many consecutive nights to rank, starting from at.
sun_depression_degnodegrees, 6 to 3018 (astronomical twilight)How far below the horizon the Sun must be for the sky to count as dark.
moon_altitude_max_degnodegrees, -90 to 900 (Moon below the horizon)The Moon must be at or below this altitude for a minute to count as moonless.
moon_illumination_maxnofraction, 0 to 11 (no illumination filter)Caps how illuminated the Moon may be and still count a minute as usable, for example 0.15 to allow a thin crescent above the horizon.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.

Formats: json, csv   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/dark-window?lat=51.509&lon=-0.126&at=2026-11-14&tz=Europe/London&nights=3
Example response (real, captured live)
{
  "endpoint": "/v2/dark-window",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "at": "2026-11-14T00:00:00.000Z",
    "mode": "instant",
    "resolved_from": "at",
    "is_now": false,
    "latitude": 51.509,
    "longitude": -0.126,
    "elevation_m": 0,
    "tz": "Europe/London",
    "utc_offset_seconds": 0,
    "utc_offset_iso": "+00:00",
    "is_dst": false,
    "timezone_source": "parameter",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "nights": 3,
    "sun_depression_deg": 18,
    "moon_altitude_max_deg": 0,
    "moon_illumination_max": 1,
    "ignored": []
  },
  "data": {
    "method": {
      "sun_test": "Sun's geometric centre at or below -18 deg",
      "moon_test": "Moon below the horizon by the engine's own rise and set search, which applies topocentric parallax and refraction to the upper limb",
      "illumination_override_offered": false,
      "illumination_override_applied": false,
      "boundary_precision_seconds": 1,
      "note": "Boundaries are root-found, not sampled, through the same rise and set search /v2/rise-set uses, so a moonset here and a moonset there agree to within the root finder's own tolerance of a few milliseconds."
    },
    "high_latitude_caution": false,
    "high_latitude_note": null,
    "best_night_index": 0,
    "ranked": [
      0,
      1,
      2
    ],
    "nights": [
      {
        "night_of": "2026-11-14",
        "label": "Night of Saturday 14 November 2026",
        "dark_interval": {
          "kind": "astronomical_night",
          "label": "Astronomical night",
          "start": "2026-11-14T18:09:13.058+00:00",
          "end": "2026-11-15T05:21:23.662+00:00",
          "duration_minutes": 672.2,
          "start_reason": "sun_reached_depression",
          "end_reason": "sun_left_depression",
          "open_start": false,
          "open_end": false
        },
        "special": null,
        "usable_intervals": [
          {
            "kind": "moonless_dark",
            "label": "Moonless dark",
            "start": "2026-11-14T19:50:10.104+00:00",
            "end": "2026-11-15T05:21:23.662+00:00",
            "duration_minutes": 571.2,
            "start_reason": "moon_set",
            "end_reason": "sun_left_depression",
            "open_start": false,
            "open_end": false
          }
        ],
        "usable_minutes": 571.2,
        "moonlit_dark_minutes": 101,
        "usable_fraction": 0.8497,
        "moon": {
          "illuminated_fraction_at_midpoint": 0.271392,
          "phase_name": "waxing_crescent",
          "rise": "2026-11-14T12:16:26.512+00:00",
          "set": "2026-11-14T19:50:10.104+00:00",
          "up_at_dusk": true,
          "up_for_whole_dark_interval": false
        },
        "illumination_override_applied": false,
        "verdict": "good",
        "verdict_reason": "Between 60 and 85 percent of astronomical night is moonless."
      },
      {
        "night_of": "2026-11-15",
        "label": "Night of Sunday 15 November 2026",
        "dark_interval": {
          "kind": "astronomical_night",
          "label": "Astronomical night",
          "start": "2026-11-15T18:08:07.923+00:00",
          "end": "2026-11-16T05:22:48.657+00:00",
          "duration_minutes": 674.7,
          "start_reason": "sun_reached_depression",
          "end_reason": "sun_left_depression",
          "open_start": false,
          "open_end": false
        },
        "special": null,
        "usable_intervals": [
          {
            "kind": "moonless_dark",
            "label": "Moonless dark",
            "start": "2026-11-15T21:02:13.762+00:00",
            "end": "2026-11-16T05:22:48.657+00:00",
            "duration_minutes": 500.6,
            "start_reason": "moon_set",
            "end_reason": "sun_left_depression",
            "open_start": false,
            "open_end": false
          }
        ],
        "usable_minutes": 500.6,
        "moonlit_dark_minutes": 174.1,
        "usable_fraction": 0.742,
        "moon": {
          "illuminated_fraction_at_midpoint": 0.35895,
          "phase_name": "waxing_crescent",
          "rise": "2026-11-15T12:40:39.487+00:00",
          "set": "2026-11-15T21:02:13.762+00:00",
          "up_at_dusk": true,
          "up_for_whole_dark_interval": false
        },
        "illumination_override_applied": false,
        "verdict": "good",
        "verdict_reason": "Between 60 and 85 percent of astronomical night is moonless."
      },
      {
        "night_of": "2026-11-16",
        "label": "Night of Monday 16 November 2026",
        "dark_interval": {
          "kind": "astronomical_night",
          "label": "Astronomical night",
          "start": "2026-11-16T18:07:05.191+00:00",
          "end": "2026-11-17T05:24:12.871+00:00",
          "duration_minutes": 677.1,
          "start_reason": "sun_reached_depression",
          "end_reason": "sun_left_depression",
          "open_start": false,
          "open_end": false
        },
        "special": null,
        "usable_intervals": [
          {
            "kind": "moonless_dark",
            "label": "Moonless dark",
            "start": "2026-11-16T22:15:31.163+00:00",
            "end": "2026-11-17T05:24:12.871+00:00",
            "duration_minutes": 428.7,
            "start_reason": "moon_set",
            "end_reason": "sun_left_depression",
            "open_start": false,
            "open_end": false
          }
        ],
        "usable_minutes": 428.7,
        "moonlit_dark_minutes": 248.4,
        "usable_fraction": 0.6331,
        "moon": {
          "illuminated_fraction_at_midpoint": 0.452732,
          "phase_name": "first_quarter",
          "rise": "2026-11-16T12:59:01.516+00:00",
          "set": "2026-11-16T22:15:31.163+00:00",
          "up_at_dusk": true,
          "up_for_whole_dark_interval": false
        },
        "illumination_override_applied": false,
        "verdict": "good",
        "verdict_reason": "Between 60 and 85 percent of astronomical night is moonless."
      }
    ],
    "summary": {
      "nights_returned": 3,
      "best_night_of": "2026-11-14",
      "best_usable_minutes": 571.2,
      "total_usable_minutes": 1500.5,
      "trend": "declining",
      "trend_note": "The moonless window shortens by about 71.3 minutes on each successive night across this run."
    }
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/dark-window?at=2026-11-14&lat=51.509&lon=-0.126&nights=3&tz=Europe%2FLondon",
    "docs": "https://www.cyclecalcs.com/api/reference.html#dark-window",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/sunrise-sunset-calculator.html#dw-section"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#dark-window"
}
GET /v2/sidereal-time Phase 3

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.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
stepnoduration: 1min, 1h, 6h, 1d, 7d, 1mo, 1yendpoint-specificThe stride of a range query, from 1 minute to 1 year. Only meaningful together with start.
countnointegerendpoint-specificHow many rows to return from start. Mutually exclusive with end.
latnodecimal degrees, -90 to 90(none)Accepted for uniformity with every other endpoint. Sidereal time is a function of longitude alone; the response states this and lat has no effect on any published value.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv, txt.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.

Formats: json, csv, txt   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/sidereal-time?lon=-0.126&lat=51.509&at=2026-11-14T21:30:00Z
Example response (real, captured live)
{
  "endpoint": "/v2/sidereal-time",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "at": "2026-11-14T21:30:00.000Z",
    "mode": "instant",
    "resolved_from": "at",
    "is_now": false,
    "latitude": 51.509,
    "longitude": -0.126,
    "elevation_m": 0,
    "tz": "UTC",
    "utc_offset_seconds": 0,
    "utc_offset_iso": "+00:00",
    "is_dst": false,
    "timezone_source": "default",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "ignored": []
  },
  "data": {
    "instant": "2026-11-14T21:30:00.000Z",
    "julian_date_ut": 2461359.3958333335,
    "greenwich": {
      "apparent_hours": 1.099745,
      "apparent_hms": "01h 05m 59.083s",
      "apparent_deg": 16.49618,
      "mean_hours": 1.099602,
      "mean_hms": "01h 05m 58.568s",
      "mean_deg": 16.494035
    },
    "local": {
      "longitude_deg": -0.126,
      "apparent_hours": 1.091345,
      "apparent_hms": "01h 05m 28.843s",
      "apparent_deg": 16.37018,
      "mean_hours": 1.091202,
      "mean_hms": "01h 05m 28.328s",
      "mean_deg": 16.368035,
      "meridian_right_ascension_hours": 1.091345
    },
    "equation_of_equinoxes": {
      "seconds_of_time": 0.514833,
      "arcseconds": 7.722494,
      "definition": "apparent sidereal time minus mean sidereal time, equal to the nutation in longitude times the cosine of the mean obliquity"
    },
    "nutation": {
      "longitude_arcsec": 8.416828,
      "obliquity_arcsec": 7.308326,
      "mean_obliquity_deg": 23.435784,
      "true_obliquity_deg": 23.437814
    },
    "notes": {
      "engine_returns": "The engine's SiderealTime function returns Greenwich apparent sidereal time in hours. Mean sidereal time is derived here by subtracting the equation of the equinoxes.",
      "local_identity": "local = greenwich + longitude / 15, in hours, wrapped into the range 0 to 24. East longitude is positive.",
      "meridian_meaning": "Local apparent sidereal time is the right ascension currently on your meridian. A star with that right ascension is at its highest right now.",
      "latitude_independence": "Sidereal time does not depend on latitude. A latitude parameter is accepted and echoed but never used.",
      "ut1": "Sidereal time is strictly a function of UT1. No IERS bulletin is loaded, so UT1 is assumed equal to UTC and the result may be out by up to 0.9 s of time, which is up to about 13.5 arcseconds of hour angle."
    }
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/sidereal-time?at=2026-11-14T21%3A30%3A00Z&lat=51.509&lon=-0.126",
    "docs": "https://www.cyclecalcs.com/api/reference.html#sidereal-time",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/local-sidereal-time.html",
    "explain": "https://www.cyclecalcs.com/learn/sidereal-solar-day.html"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#sidereal-time"
}
GET /v2/equation-of-time Phase 3

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.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
stepnoduration: 1min, 1h, 6h, 1d, 7d, 1mo, 1yendpoint-specificThe stride of a range query, from 1 minute to 1 year. Only meaningful together with start.
countnointegerendpoint-specificHow many rows to return from start. Mutually exclusive with end.
latnodecimal degrees, -90 to 90(none)Accepted for uniformity with every other endpoint. This endpoint is a function of longitude alone; lat has no effect on any published value.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
includenocomma-separated: basic, extremesbasicbasic is the equation-of-time value and the Sun's place at each instant. extremes adds the year's fast/slow turning points for an analemma-style range query.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv, txt.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.

Formats: json, csv, txt   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/equation-of-time?lon=-0.126&at=2026-11-14T21:30:00Z
Example response (real, captured live)
{
  "endpoint": "/v2/equation-of-time",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "at": "2026-11-14T21:30:00.000Z",
    "mode": "instant",
    "resolved_from": "at",
    "is_now": false,
    "latitude": null,
    "longitude": -0.126,
    "elevation_m": 0,
    "tz": "UTC",
    "utc_offset_seconds": 0,
    "utc_offset_iso": "+00:00",
    "is_dst": false,
    "timezone_source": "default",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "include": [
      "basic"
    ],
    "ignored": []
  },
  "data": {
    "instant": "2026-11-14T21:30:00.000Z",
    "equation_of_time_minutes": 15.513533,
    "equation_of_time_seconds": 930.811956,
    "equation_of_time_hms": "+00h 15m 30.8s",
    "sun": {
      "declination_deg": -18.400381,
      "right_ascension_hours": 15.341186,
      "ecliptic_longitude_deg": 232.525452,
      "distance_au": 0.989369
    },
    "meridian": {
      "longitude_deg": -0.126,
      "mean_noon_utc": "2026-11-14T12:00:30.240Z",
      "apparent_noon_utc": "2026-11-14T11:44:55.882Z",
      "mean_noon_local": "2026-11-14T12:00:30.240Z",
      "apparent_noon_local": "2026-11-14T11:44:55.882Z",
      "sundial_correction_minutes": -15.572633,
      "sundial_correction_note": "Subtract 15.6 minutes from a sundial reading on this meridian to get mean solar time, then apply your zone offset to get clock time.",
      "note": "Apparent noon is a true upper transit of the Sun, root-found by the engine, not the midpoint of sunrise and sunset. Latitude does not move it."
    },
    "sign_convention": "apparent solar time minus mean solar time. Positive means the Sun is ahead of the clock, so a sundial reads later than a clock keeping mean time on the same meridian.",
    "notes": {
      "components": "This endpoint does not publish the eccentricity and obliquity components separately. Those two terms are halves of a teaching approximation and they sum to that approximation, not to the exact value returned here, so this API will not print two numbers that do not add up to the third. See https://www.cyclecalcs.com/learn/equation-of-time.html for the two effects and why they combine the way they do.",
      "method": "Computed from the engine's Sun. The apparent solar time is derived from the Sun's true hour angle; the mean solar time is UT plus the meridian's longitude offset. No day-of-year series is used anywhere.",
      "analemma": "Plot equation_of_time_minutes on the horizontal axis against sun.declination_deg on the vertical axis over a year with step=1d and you have the analemma."
    }
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/equation-of-time?at=2026-11-14T21%3A30%3A00Z&lon=-0.126",
    "docs": "https://www.cyclecalcs.com/api/reference.html#equation-of-time",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "explain": "https://www.cyclecalcs.com/learn/equation-of-time.html"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#equation-of-time"
}
GET /v2/libration Phase 3

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.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
stepnoduration: 1min, 1h, 6h, 1d, 7d, 1mo, 1yendpoint-specificThe stride of a range query, from 1 minute to 1 year. Only meaningful together with start.
countnointegerendpoint-specificHow many rows to return from start. Mutually exclusive with end.
latnodecimal degrees, -90 to 90(none unless place resolves it)Latitude, north positive. Sent together with lon (or neither), unless place supplies both. Do not append N or S.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
includenocomma-separated: basic, terminatorbasicbasic is libration in longitude/latitude, the sub-Earth point and the position angles. terminator adds the sub-solar point and the colongitude that fixes where the terminator falls.
refractionnonormal | nonenormalnormal applies the Saemundsson refraction model near the horizon; none reports the geometric, unrefracted altitude. Both are always published side by side in the response regardless of this setting.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.

Formats: json, csv   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/libration?at=2026-11-14T21:30:00Z&include=basic,terminator
Example response (real, captured live)
{
  "endpoint": "/v2/libration",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "at": "2026-11-14T21:30:00.000Z",
    "mode": "instant",
    "resolved_from": "at",
    "is_now": false,
    "latitude": null,
    "longitude": null,
    "elevation_m": 0,
    "tz": "UTC",
    "utc_offset_seconds": 0,
    "utc_offset_iso": "+00:00",
    "is_dst": false,
    "timezone_source": "default",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "include": [
      "basic",
      "terminator"
    ],
    "refraction": "normal",
    "ignored": []
  },
  "data": {
    "instant": "2026-11-14T21:30:00.000Z",
    "libration": {
      "longitude_deg": -2.476857,
      "latitude_deg": 3.44877,
      "total_deg": 4.246037,
      "favoured_limb": "north-west",
      "favoured_limb_note": "The Moon is tilted so that the northern limb and the western limb, toward Grimaldi and Mare Orientale are turned toward us, bringing terrain into view that is usually hidden. Selenographic east is the opposite side of the disc from sky east."
    },
    "sub_earth_point": {
      "selenographic_longitude_deg": -2.476857,
      "selenographic_latitude_deg": 3.44877,
      "definition": "the point on the Moon's surface at the centre of the disc as seen from the centre of the Earth. Its selenographic coordinates are the libration angles."
    },
    "sub_solar_point": {
      "selenographic_longitude_deg": 115.699097,
      "selenographic_latitude_deg": -1.55599,
      "selenographic_colongitude_deg": 334.300903,
      "colongitude_definition": "the sub-solar selenographic longitude subtracted from 90 deg, wrapped into the range 0 to 360. It is near 270 at new moon, 0 at first quarter, 90 at full moon and 180 at last quarter, offset by the libration in longitude, and it fixes where the terminator falls.",
      "terminator_note": "The sunrise terminator lies at the selenographic longitude given by 360 deg minus the colongitude, which is 90 deg west of the sub-solar point. Craters near it show their best relief, because the Sun is low there. The sunset terminator is 180 deg minus the colongitude."
    },
    "axis": {
      "position_angle_deg": 351.234832,
      "position_angle_definition": "the direction of the Moon's north pole on the sky, measured from celestial north through east, at the geocentre.",
      "bright_limb_position_angle_deg": 261.307825,
      "bright_limb_definition": "the direction of the midpoint of the bright limb, measured from celestial north through east."
    },
    "geometry": {
      "distance_km": 404878.81927,
      "angular_diameter_deg": 0.491732,
      "angular_diameter_arcmin": 29.503937,
      "ecliptic_longitude_deg": 294.139066,
      "ecliptic_latitude_deg": -2.59988,
      "illuminated_fraction": 0.263572,
      "phase_angle_deg": 61.610136,
      "phase_name": "waxing_crescent"
    },
    "method": {
      "libration_source": "the engine's Libration function, which uses the Moon's physical libration model",
      "colongitude_source": "computed from RotationAxis(Body.Moon) using the IAU WGCCRE 2015 rotation elements, because the engine exposes no sub-solar function",
      "annual_aberration_applied": true,
      "aberration_note": "The engine back-dates every position it returns for light travel time, so light time is already applied before the sub-solar direction is formed. What this endpoint applies on top is annual aberration, the Earth's displacement over one light time, worth about 20.5 arcsec of direction and 0.006 deg of colongitude.",
      "cross_check_residual_deg": 0.020123,
      "cross_check_note": "The sub-Earth point recomputed from the WGCCRE rotation elements agrees with the Libration function's own angles to about 0.02 deg. That residual is the difference between the two models and is published rather than hidden.",
      "favoured_limb_dead_band_deg": 0.5,
      "position_angle_frame": "ICRF-aligned mean equator and equinox of J2000.0 (EQJ)"
    }
  },
  "warnings": [],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/libration?at=2026-11-14T21%3A30%3A00Z&include=basic%2Cterminator",
    "docs": "https://www.cyclecalcs.com/api/reference.html#libration",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/moon-phase-calendar.html",
    "explain": "https://www.cyclecalcs.com/learn/tidal-locking.html",
    "related": [
      {
        "rel": "cycle",
        "title": "The anomalistic month",
        "href": "https://www.cyclecalcs.com/cycles/anomalistic-month.html"
      },
      {
        "rel": "cycle",
        "title": "The draconic month",
        "href": "https://www.cyclecalcs.com/cycles/draconic-month.html"
      }
    ]
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#libration"
}
GET /v2/jupiter-moons Phase 3

Where the four Galilean moons are, and every transit, shadow transit, eclipse and occultation in the window, with both contact instants for each.

ParameterRequiredTypeDefaultDescription
atnoISO 8601 date or datetimenowThe instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone; a value ending in Z or a numeric offset is an absolute instant; a value with neither is read in tz (or UTC if none applies). Aliases: date, datetime, t, utc. Cannot be combined with start.
startnoISO 8601 date or datetime(none; at is used instead)Opens a range query. Combine with end, or with step and/or count. Alias: from. Cannot be combined with at.
endnoISO 8601 date or datetime(none)Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count.
stepnoduration, 10s to 600s(an internal scan stride)Sample stride for the contact search. Narrower than the API-wide 1min-to-1y range: a Galilean contact takes a few minutes, so this endpoint enforces its own bounds.
latnodecimal degrees, -90 to 90(none unless place resolves it)Latitude, north positive. Sent together with lon (or neither), unless place supplies both. Do not append N or S.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
moonsnocomma-separated: Io, Europa, Ganymede, Callistoall fourRestrict the scan to specific moons.
eventsnocomma-separated: transit, shadow, eclipse, occultationall fourRestrict the scan to specific event families: the moon crossing Jupiter's disc, its shadow crossing the disc, the moon entering Jupiter's shadow, or Jupiter occulting the moon.
refractionnonormal | nonenormalnormal applies the Saemundsson refraction model near the horizon; none reports the geometric, unrefracted altitude. Both are always published side by side in the response regardless of this setting.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.

Formats: json, csv   Cache class: fixed_instant

This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. Cache-Control: public, max-age=31536000, immutable. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400.

Example request

GET https://www.cyclecalcs.com/v2/jupiter-moons?at=2026-11-15T09:40:00Z&step=1m
Example response (real, captured live)
{
  "endpoint": "/v2/jupiter-moons",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "at": "2026-11-15T09:40:00.000Z",
    "mode": "instant",
    "resolved_from": "at",
    "is_now": false,
    "latitude": null,
    "longitude": null,
    "elevation_m": 0,
    "tz": "UTC",
    "utc_offset_seconds": 0,
    "utc_offset_iso": "+00:00",
    "is_dst": false,
    "timezone_source": "default",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "moons": [
      "io",
      "europa",
      "ganymede",
      "callisto"
    ],
    "events": [
      "transit",
      "shadow",
      "eclipse",
      "occultation"
    ],
    "step": "1m",
    "refraction": "normal",
    "ignored": []
  },
  "data": {
    "instant": "2026-11-15T09:40:00.000Z",
    "jupiter": {
      "distance_au": 5.277411,
      "angular_equatorial_radius_arcsec": 18.678254,
      "angular_polar_radius_arcsec": 17.466513,
      "equatorial_radius_km": 71492,
      "polar_radius_km": 66854,
      "axis_position_angle_deg": 21.871395,
      "magnitude": -2.1,
      "phase_angle_deg": 10.700272
    },
    "moons": [
      {
        "name": "io",
        "label": "Io",
        "radius_km": 1821.6,
        "offset_east_rj": 0.880776,
        "offset_north_rj": -0.327827,
        "offset_line_of_sight_rj": -5.80306,
        "offset_east_arcsec": 16.451364,
        "offset_north_arcsec": -6.123242,
        "in_front": true,
        "state": "transiting",
        "shadow_offset_east_rj": 0.026452,
        "shadow_offset_north_rj": -0.021245,
        "shadow_frame": "the same sky frame as the moon offsets, so both can be drawn on one disc",
        "shadow_on_disc": true
      },
      {
        "name": "europa",
        "label": "Europa",
        "radius_km": 1560.8,
        "offset_east_rj": 1.01766,
        "offset_north_rj": -0.528439,
        "offset_line_of_sight_rj": 9.231564,
        "offset_east_arcsec": 19.008113,
        "offset_north_arcsec": -9.870319,
        "in_front": false,
        "state": "clear",
        "shadow_offset_east_rj": null,
        "shadow_offset_north_rj": null,
        "shadow_frame": "the same sky frame as the moon offsets, so both can be drawn on one disc",
        "shadow_on_disc": false
      },
      {
        "name": "ganymede",
        "label": "Ganymede",
        "radius_km": 2631.2,
        "offset_east_rj": 3.714806,
        "offset_north_rj": -1.54809,
        "offset_line_of_sight_rj": 14.461625,
        "offset_east_arcsec": 69.386082,
        "offset_north_arcsec": -28.915615,
        "in_front": false,
        "state": "clear",
        "shadow_offset_east_rj": null,
        "shadow_offset_north_rj": null,
        "shadow_frame": "the same sky frame as the moon offsets, so both can be drawn on one disc",
        "shadow_on_disc": false
      },
      {
        "name": "callisto",
        "label": "Callisto",
        "radius_km": 2410.3,
        "offset_east_rj": -11.611475,
        "offset_north_rj": 4.449431,
        "offset_line_of_sight_rj": 23.277377,
        "offset_east_arcsec": -216.882078,
        "offset_north_arcsec": 83.107603,
        "in_front": false,
        "state": "clear",
        "shadow_offset_east_rj": null,
        "shadow_offset_north_rj": null,
        "shadow_frame": "the same sky frame as the moon offsets, so both can be drawn on one disc",
        "shadow_on_disc": false
      }
    ],
    "window": {
      "start": "2026-11-15T00:00:00.000Z",
      "end": "2026-11-16T00:00:00.000Z",
      "anchor": "utc",
      "length_minutes": 1440,
      "measured_from": "at"
    },
    "events": [
      {
        "kind": "moon_occultation_ingress",
        "label": "Ganymede passes behind Jupiter",
        "instant": "2026-11-15T00:17:57.000Z",
        "instant_precision_seconds": 1,
        "body": "Ganymede",
        "value_deg": null,
        "value_unit": null,
        "days_from_query": -0.390312,
        "contact_interior_instant": "2026-11-15T00:26:04.000Z",
        "contact_note": "instant is first contact, when the leading edge touches Jupiter's limb. contact_interior_instant is the later moment when the whole body is on the disc.",
        "href": "https://www.cyclecalcs.com/planets/jupiter.html"
      },
      {
        "kind": "moon_eclipse_ingress",
        "label": "Europa enters Jupiter shadow",
        "instant": "2026-11-15T04:03:59.000Z",
        "instant_precision_seconds": 1,
        "body": "Europa",
        "value_deg": null,
        "value_unit": null,
        "days_from_query": -0.233345,
        "contact_interior_instant": "2026-11-15T04:07:46.000Z",
        "contact_note": "instant is first contact, when the leading edge touches Jupiter's limb. contact_interior_instant is the later moment when the whole body is on the disc.",
        "href": "https://www.cyclecalcs.com/planets/jupiter.html"
      },
      {
        "kind": "moon_occultation_egress",
        "label": "Ganymede emerges from behind Jupiter",
        "instant": "2026-11-15T04:05:55.000Z",
        "instant_precision_seconds": 1,
        "body": "Ganymede",
        "value_deg": null,
        "value_unit": null,
        "days_from_query": -0.232002,
        "contact_interior_instant": "2026-11-15T03:57:47.000Z",
        "contact_note": "instant is last contact, when the trailing edge leaves Jupiter's limb. contact_interior_instant is the EARLIER moment when the leading edge first touched the limb from inside.",
        "href": "https://www.cyclecalcs.com/planets/jupiter.html"
      },
      {
        "kind": "moon_occultation_ingress",
        "label": "Europa passes behind Jupiter",
        "instant": "2026-11-15T06:34:06.000Z",
        "instant_precision_seconds": 1,
        "body": "Europa",
        "value_deg": null,
        "value_unit": null,
        "days_from_query": -0.129097,
        "contact_interior_instant": "2026-11-15T06:37:55.000Z",
        "contact_note": "instant is first contact, when the leading edge touches Jupiter's limb. contact_interior_instant is the later moment when the whole body is on the disc.",
        "href": "https://www.cyclecalcs.com/planets/jupiter.html"
      },
      "... 6 more rows omitted for length ..."
    ],
    "events_found": 10,
    "scan": {
      "stride": "1m",
      "samples_evaluated": 1441,
      "refinement": "root bracketing on the limb-distance function with an interpolated split, terminating when the bracket is under 1 second",
      "note": "Two contacts closer together than the stride may be reported as one. Reduce step to separate them."
    },
    "method": {
      "moon_positions": "JupiterMoons evaluated at Jupiter's light departure time from BackdatePosition",
      "light_time_residual_km": 5,
      "light_time_note": "The differential light time between Jupiter and a moon is not solved separately. It is at most 0.3 s of arrival time for Callisto, which moves it by under 5 km.",
      "disc_figure": "oblate, from RotationAxis(Body.Jupiter), equatorial 71492 km and polar 66854 km",
      "penumbra_half_width_km": {
        "io": 367,
        "europa": 581,
        "ganymede": 938,
        "callisto": 1649
      },
      "eclipse_note": "The umbra is treated as a cylinder with the Sun as a point source. The Sun's angular radius at Jupiter is about 0.05 deg, so the shadow has a penumbral fringe of the width above and a real disappearance is gradual over roughly two to four minutes rather than instantaneous.",
      "views": "Transits and occultations are computed from the Earth view; shadow transits and eclipses from the Sun view. The two do not coincide: before opposition a moon's shadow crosses Jupiter before the moon does, and after opposition the order reverses."
    }
  },
  "warnings": [
    {
      "code": "oblateness_simplified",
      "message": "Jupiter's projected polar semi-axis is taken as the full polar radius. Jupiter's axial tilt is about 3 deg, so the error is well under a kilometre.",
      "field": "events",
      "value": null
    }
  ],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/jupiter-moons?at=2026-11-15T09%3A40%3A00Z&step=1m",
    "docs": "https://www.cyclecalcs.com/api/reference.html#jupiter-moons",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/planets/jupiter.html",
    "explain": "https://www.cyclecalcs.com/learn/tidal-locking.html",
    "related": []
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#jupiter-moons"
}

Some arrays are shortened above for length; the live response returns every row.

GET /v2/sky-quality Phase 3

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.

ParameterRequiredTypeDefaultDescription
bortlenointeger, 1 to 9(required, unless nelm is sent)The Bortle dark-sky class of the site, 1 (darkest) to 9 (inner-city). One of bortle or nelm is required; the API infers no sky class from a location.
nelmnomagnitude, 2.0 to 8.5(required, unless bortle is sent)A measured naked-eye limiting magnitude, used instead of a Bortle class. Mutually exclusive with bortle.
equipmentnonaked_eye | binoculars | telescopenaked_eyeWhat you are observing with. telescope requires aperture_mm.
aperture_mmnomillimetres, 5 to 5000(required when equipment=telescope)The aperture of the telescope or binoculars. Ignored, with a warning, when equipment=naked_eye.
latnodecimal degrees, -90 to 90(none unless place resolves it)Latitude, north positive. Sent together with lon (or neither), unless place supplies both. Do not append N or S.
lonnodecimal degrees, -180 to 180(none unless place resolves it)Longitude, east positive. Aliases: lng, longitude. Sent together with lat. Do not append E or W.
elevation_mnometres, -500 to 90000Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modelled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height.
placenostring: name, "name,COUNTRY", or "id:<place_id>"(none)A place name resolved against the GeoNames-derived index instead of sending lat/lon directly. Supplies latitude, longitude and, unless tz is also sent, the effective time zone. Cannot be combined with lat/lon. An ambiguous match is disclosed with alternatives; send place=id:<place_id> from a /v2/places search to pin one exactly. A resolved place carries meta.rights = attribution_required (GeoNames, CC BY 4.0).
place_strategynobest | errorbestHow an ambiguous place name is handled. best picks the most populous candidate and warns; error returns 400 AMBIGUOUS_PLACE with the candidate list instead of guessing.
atnoISO 8601 date or datetimenowThis endpoint has no range mode; it always answers for one instant.
tznoIANA zone name (Europe/London) or UTCUTC, or the resolved place's zoneThe time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. tz=auto is reserved and refused.
ambiguousnofirst | last | errorfirstHow a local time that occurred twice (a clocks-back transition) is resolved. error returns 409 AMBIGUOUS_LOCAL_TIME with both candidate instants instead of choosing.
nonexistentnoerror | shift_forward | shift_backerrorHow a local time that never happened (a clocks-forward gap) is resolved. error returns 400 NONEXISTENT_LOCAL_TIME with the gap; the shift options move the instant across it.
includenocomma-separated: showpieces, ladder, sky_conditionsshowpiecesshowpieces judges a fixed list of well-known deep-sky targets against this sky and kit. ladder adds the reach of each equipment tier for comparison. sky_conditions echoes the resolved bortle/nelm pair and how it was derived.
refractionnonormal | nonenormalnormal applies the Saemundsson refraction model near the horizon; none reports the geometric, unrefracted altitude. Both are always published side by side in the response regardless of this setting.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.
formatnoenumjsonResponse format. This endpoint serves: json, csv, txt.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.

Formats: json, csv, txt   Cache class: reference

Static reference data. It changes only when the API itself changes, not on a schedule, so it is cached longer than a now-relative answer while staying safe to re-fetch. Cache-Control: public, max-age=300. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=3600, stale-while-revalidate=86400.

Example request

GET https://www.cyclecalcs.com/v2/sky-quality?bortle=3&equipment=telescope&aperture_mm=200
Example response (real, captured live)
{
  "endpoint": "/v2/sky-quality",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "at": "2026-08-05T12:00:00.000Z",
    "mode": "instant",
    "resolved_from": "default_now",
    "is_now": true,
    "latitude": null,
    "longitude": null,
    "elevation_m": 0,
    "tz": "UTC",
    "utc_offset_seconds": 0,
    "utc_offset_iso": "+00:00",
    "is_dst": false,
    "timezone_source": "default",
    "time_format": "iso",
    "format": "json",
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "bortle": 3,
    "nelm": null,
    "equipment": "telescope",
    "aperture_mm": 200,
    "include": [
      "showpieces"
    ],
    "refraction": "normal",
    "ignored": []
  },
  "data": {
    "model": {
      "name": "CycleCalcs Bortle plus aperture limiting-magnitude estimate",
      "not_a_measurement": true,
      "kind": "empirical_estimate",
      "inputs": [
        "bortle_class",
        "aperture_mm",
        "equipment"
      ],
      "statement": "Naked-eye reach comes from the Bortle dark-sky scale. Telescope reach starts from the classic aperture formula 2.7 + 5 log10(aperture in mm), the faintest star an aperture shows under a pristine sky, then subtracts an allowance for light pollution. That allowance is a tuned estimate, not a published constant. The penalty is tuned so the worked examples come out right: a 200 mm telescope reaches about 12.6 at Bortle 5.",
      "uncertainty": 1,
      "uncertainty_unit": "mag",
      "uncertainty_basis": "Real results swing a magnitude or more with eyesight and experience, how steady and clear the air is, how dark-adapted the observer is, and how high the target sits.",
      "inferred_from_location": false,
      "inference_note": "The sky class is never inferred from a location. No light-pollution dataset ships with this API, so bortle or nelm must be supplied."
    },
    "sky": {
      "bortle_class": 3,
      "bortle_class_inferred": false,
      "label": "Rural sky",
      "description": "only a faint glow on the horizon",
      "naked_eye_limiting_magnitude": 6.8,
      "light_pollution_penalty_mag": 0.5
    },
    "reach": {
      "equipment": "telescope",
      "aperture_mm": 200,
      "pristine_sky_ceiling_mag": 14.21,
      "limiting_magnitude": 13.71,
      "gain_over_naked_eye_mag": 6.91,
      "statement": "Under a rural sky (Bortle 3), a 200 mm telescope should reach about magnitude 13.71, roughly 6.91 magnitudes deeper than the unaided eye."
    },
    "showpieces": {
      "note": "These magnitudes describe point-like stars. Galaxies and nebulae spread their light over an area, so a magnitude 9 galaxy is usually far harder to see than a magnitude 9 star, and a darker sky helps those more than extra aperture does. The showpiece verdicts are judged on the sky and aperture each object really needs, not on its catalogue magnitude.",
      "judged_at_aperture_mm": 200,
      "easy_count": 8,
      "stretch_count": 1,
      "out_of_range_count": 0,
      "summary": "Of these 9 popular showpieces, 8 are within easy reach and 1 is a stretch.",
      "objects": [
        {
          "name": "Pleiades (M45)",
          "catalogue_magnitude": 1.6,
          "extended": false,
          "easiest_bortle": 8,
          "stretch_bortle": 9,
          "minimum_aperture_mm": 7,
          "verdict": "easy",
          "reason": "Straightforward at Bortle 8 or darker with at least 7 mm."
        },
        {
          "name": "Orion Nebula (M42)",
          "catalogue_magnitude": 4,
          "extended": true,
          "easiest_bortle": 7,
          "stretch_bortle": 9,
          "minimum_aperture_mm": 7,
          "verdict": "easy",
          "reason": "Straightforward at Bortle 7 or darker with at least 7 mm."
        },
        {
          "name": "Andromeda Galaxy (M31) core",
          "catalogue_magnitude": 3.4,
          "extended": true,
          "easiest_bortle": 6,
          "stretch_bortle": 8,
          "minimum_aperture_mm": 7,
          "verdict": "easy",
          "reason": "Straightforward at Bortle 6 or darker with at least 7 mm."
        },
        {
          "name": "Hercules Cluster (M13)",
          "catalogue_magnitude": 5.8,
          "extended": false,
          "easiest_bortle": 5,
          "stretch_bortle": 7,
          "minimum_aperture_mm": 50,
          "verdict": "easy",
          "reason": "Straightforward at Bortle 5 or darker with at least 50 mm."
        },
        "... 5 more rows omitted for length ..."
      ]
    }
  },
  "warnings": [
    {
      "code": "estimate_only",
      "message": "This limiting magnitude is a tuned empirical estimate with about 1 magnitude of uncertainty, not a measurement. See data.model.",
      "field": "reach.limiting_magnitude",
      "value": "13.71"
    }
  ],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/sky-quality?aperture_mm=200&bortle=3&equipment=telescope",
    "docs": "https://www.cyclecalcs.com/api/reference.html#sky-quality",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/telescope-calculator.html"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Computed by CycleCalcs (cyclecalcs.com) with the MIT-licensed Astronomy Engine. Positional astronomy only.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#sky-quality"
}

Some arrays are shortened above for length; the live response returns every row.

Places

GET /v2/places Phase 3 Conditional: attribution required

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.

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.

ParameterRequiredTypeDefaultDescription
qnostring, 2+ characters(required for a name search)A place name to search for, matched against the GeoNames-derived index. One of q, id, or lat and lon (reverse lookup) is required.
idnoa place_id, e.g. id:us-n35198-w111651(none)Looks up one exact place by its stable identifier, returned by an earlier search as data.place_id.
latnodecimal degrees, -90 to 90(none)Together with lon, performs a reverse lookup: the nearest places to this point, within radius_km.
lonnodecimal degrees, -180 to 180(none)Together with lat, performs a reverse lookup. Sent together with lat, or neither.
countrynoISO 3166-1 alpha-2 code, e.g. GB(no filter)Restricts a name search to one country.
regionnostring(no filter)Restricts a name search to one admin region (state/province) within a country.
limitnointeger, 1 to 256Maximum number of place rows to return. This is a RESULT COUNT, unlike limit on every other endpoint, which pages a series.
min_populationnointeger, 0 to 40,000,0000Excludes places under this population. The index stores population to the nearest 1,000, so a value is rounded up to the nearest stored figure, with a warning.
radius_kmnokilometres, 1 to 2000250Search radius for a reverse lookup (lat/lon). Has no effect on a name search.
formatnoenumjsonResponse format. This endpoint serves: json, csv.
fieldsnocomma-separated dot-paths(every field)Projects the response down to specific fields of data (dot-paths, for example phase.name). meta is never affected; provenance cannot be stripped.
shapenonested | flatnestednested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON.
precisionnointeger, 0 to 96Decimal places for numeric output.
prettyno0 | 111 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line.
verbositynofull | compactfullcompact drops prose strings (definitions, notes, formulas) from meta and reduces each meta.not_computed entry to just its name. Every numeric and enumerated field is kept either way.
time_formatnoiso | unixisoHow instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat.

Formats: json, csv   Cache class: reference

Static reference data. It changes only when the API itself changes, not on a schedule, so it is cached longer than a now-relative answer while staying safe to re-fetch. Cache-Control: public, max-age=300. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=3600, stale-while-revalidate=86400.

Example request

GET https://www.cyclecalcs.com/v2/places?q=cambridge&limit=5
Example response (real, captured live)
{
  "endpoint": "/v2/places",
  "computed_at": "2026-08-01T00:00:00.000Z",
  "query": {
    "q": "cambridge",
    "id": null,
    "latitude": null,
    "longitude": null,
    "mode": "instant",
    "country": null,
    "region": null,
    "limit": 5,
    "min_population": 0,
    "radius_km": null,
    "shape": "nested",
    "precision": 6,
    "pretty": 1,
    "verbosity": "full",
    "format": "json",
    "resolved_from": "q",
    "ignored": []
  },
  "data": {
    "mode": "search",
    "matched": 12,
    "found": 5,
    "results": [
      {
        "place_id": "gb-n52200-e000117",
        "name": "Cambridge",
        "region": "England",
        "country": "United Kingdom",
        "country_code": "GB",
        "latitude_deg": 52.2,
        "longitude_deg": 0.117,
        "timezone": "Europe/London",
        "population": 146000,
        "match": "exact",
        "rank": 1,
        "label": "Cambridge, England, United Kingdom",
        "coordinate_precision_m": 110
      },
      {
        "place_id": "ca-n43360-w080313",
        "name": "Cambridge",
        "region": "Ontario",
        "country": "Canada",
        "country_code": "CA",
        "latitude_deg": 43.36,
        "longitude_deg": -80.313,
        "timezone": "America/Toronto",
        "population": 130000,
        "match": "exact",
        "rank": 2,
        "label": "Cambridge, Ontario, Canada",
        "coordinate_precision_m": 110
      },
      {
        "place_id": "us-n42375-w071106",
        "name": "Cambridge",
        "region": "Massachusetts",
        "country": "United States",
        "country_code": "US",
        "latitude_deg": 42.375,
        "longitude_deg": -71.106,
        "timezone": "America/New_York",
        "population": 110000,
        "match": "exact",
        "rank": 3,
        "label": "Cambridge, Massachusetts, United States",
        "coordinate_precision_m": 110
      },
      {
        "place_id": "nz-s37878-e175440",
        "name": "Cambridge",
        "region": "Waikato Region",
        "country": "New Zealand",
        "country_code": "NZ",
        "latitude_deg": -37.878,
        "longitude_deg": 175.44,
        "timezone": "Pacific/Auckland",
        "population": 15000,
        "match": "exact",
        "rank": 4,
        "label": "Cambridge, Waikato Region, New Zealand",
        "coordinate_precision_m": 110
      },
      "... 1 more row omitted for length ..."
    ],
    "index": {
      "source": "GeoNames cities5000",
      "places_total": 69502,
      "population_floor": 5000,
      "coordinate_precision_m": 110,
      "population_rounding": 1000,
      "merged_colocated_rows": 35,
      "licence": "CC BY 4.0",
      "credit": "Place names and coordinates from GeoNames (geonames.org), CC BY 4.0.",
      "note": "Coordinates are the GeoNames place centroid, stored to a thousandth of a degree, so they are good to about 110 m. A city is kilometres across; for anything where your exact spot matters, send lat and lon.",
      "tiers_consulted": [
        "top",
        "rest"
      ]
    }
  },
  "warnings": [
    {
      "code": "place_ambiguous",
      "message": "\"cambridge\" matched 12 places. They are ranked exact before prefix before substring, then by population, then by place_id. Send country, or id=<place_id>, to be certain.",
      "field": "results",
      "value": 12
    },
    {
      "code": "place_index_tier_2",
      "message": "The long-tail index was consulted because the top tier of 12,000 places did not answer this query on its own.",
      "field": "results",
      "value": null
    },
    {
      "code": "truncated_results",
      "message": "limit=5 cut this list from 12 matches to 5.",
      "field": "matched",
      "value": "12"
    }
  ],
  "links": {
    "self": "https://www.cyclecalcs.com/v2/places?limit=5&q=cambridge",
    "docs": "https://www.cyclecalcs.com/api/reference.html#places",
    "spec": "https://www.cyclecalcs.com/v2/openapi.json",
    "page": "https://www.cyclecalcs.com/api.html"
  },
  "meta": "... see 'Shared parameters and conventions' above: engine credit, cache class, accuracy statement, time and rights blocks ...",
  "attribution": "Place names and coordinates from GeoNames (geonames.org), CC BY 4.0.",
  "docs": "https://www.cyclecalcs.com/api/reference.html#places"
}

Some arrays are shortened above for length; the live response returns every row.