API Reference
The complete parameter reference for every route of the CycleCalcs v2 astronomy API: 31 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 unrecognized 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.
| Parameter | Type | Default | Description |
|---|---|---|---|
at | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | ISO 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. |
end | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
step | duration: 1min, 1h, 6h, 1d, 7d, 1mo, 1y | endpoint-specific | The stride of a range query, from 1 minute to 1 year. Several endpoints narrow it; each says so in its own parameter table, and /v2/conventions publishes every narrowing under limits.step_grammar.endpoint_policies. Only meaningful together with start. |
count | integer | endpoint-specific | How many rows to return from start. Mutually exclusive with end. |
lat | decimal 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. |
lon | decimal 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_m | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | string: 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_strategy | best | error | best | How 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. |
tz | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | first | last | error | first | How 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. |
nonexistent | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
day_anchor | civil | solar | utc | civil if tz or place set a zone, else solar | What 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_format | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | enum | json | Response format. This endpoint serves: json, .... |
fields | comma-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. |
shape | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | full | compact | full | compact 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. |
limit | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | opaque 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. |
refraction | normal | none | normal | normal 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. |
frames | minimal | standard | all | standard | How 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. |
depressions | comma-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 (accepted and ignored, with a warning: there is no direct key store, and the paid tiers authenticate at the RapidAPI gateway, so no query parameter changes a tier), 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-Methods | GET, HEAD, OPTIONS |
Access-Control-Allow-Headers | Content-Type, Authorization, X-Api-Key, If-None-Match |
Access-Control-Expose-Headers | ETag, RateLimit, RateLimit-Policy, Retry-After, X-Request-Id, Server-Timing, Link |
Access-Control-Max-Age | 86400 |
The anonymous tier, which every request outside a paid RapidAPI subscription is served at, 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 catalog 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 31 times; two real examples follow, one for the ordinary case and one for the single route with a license condition attached.
meta on an ordinary (unrestricted) response, from the /v2/sun example below:
{
"api_version": "2",
"contract_version": "2.7.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: a time you give is read as UT1, and UT1 minus UTC stays under 0.9 s",
"delta_t_seconds": 69.2,
"delta_t_model": "IERS, Espenak-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": "center 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 modeled. 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.7.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-normalized, 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.",
"other_names": "A place is also found by its Latin-script GeoNames alternate names: those in its own country's languages when written in Latin letters, English ones (former names included), and spellings that differ only in spacing. \"Praha\" finds Prague, \"Saigon\" finds Ho Chi Minh City, \"Danang\" finds Da Nang. The name returned is still the GeoNames name, and matched_alias says which name matched. A search that is exactly the name of a place in the index is answered by names alone, so an alternate name never costs a place its own name."
},
"budget": {
"engine_calls": 0
}
}
Field definitions
The rows of /v2/moon, /v2/planet-board, /v2/planet-events, /v2/positions, /v2/retrogrades, /v2/sun and /v2/today carry one or both of these labels, which answer different questions. The same definitions are served as data at /v2/conventions under field_definitions, from the one source this page is generated from.
| Field | Definition |
|---|---|
tropical_sign | The 30-degree sector of the body's apparent ecliptic longitude in the true ecliptic and equinox of date (frame ect), counted from the moving March equinox: Aries 0 to 30 deg, Taurus 30 to 60 deg, and so on to Pisces 330 to 360 deg. A named coordinate bin, not a constellation: because the equinox precesses, the bins drift against the stars by about 1 deg every 71.6 years, at the measured IAU 2006 precession rate. Taken from the geocentric place on /v2/positions, /v2/moon and /v2/planet-board, even when lat and lon make a position block topocentric: /v2/positions says so in meta.conventions.label_origin and /v2/planet-board in position.ecliptic_origin. With a location the Moon's topocentric longitude can sit up to about 1 deg from the geocentric one its sector is taken from. On /v2/sun, which always has a location, it is taken from the topocentric place, at most about 9 arcsec from the geocentric one. Emitted for Solar System bodies only. |
constellation | The IAU constellation containing the position: one of the 88 regions whose boundaries Delporte drew for the IAU in 1930 along lines of right ascension and declination of the B1875.0 equinox. The J2000 right ascension and declination are precessed to B1875.0 and looked up there. A body off the ecliptic can sit outside the zodiac constellations, so the constellation and tropical_sign often disagree: Saturn at 2026-10-04T12:00:00Z is at ecliptic longitude 11.30 deg and latitude -2.71 deg, in the Aries bin of tropical_sign and in the constellation Cetus. Taken from the geocentric place on /v2/positions, /v2/moon, /v2/today, /v2/retrogrades and /v2/planet-events, even when lat and lon make a position block topocentric, and on /v2/positions from the heliocentric place under frame=heliocentric. On /v2/planet-board (with a location, unless frames=minimal) and /v2/sun it is taken from the topocentric place, which sits at most the body's parallax from the geocentric one: about 9 arcsec for the Sun and 33 arcsec for Venus at its closest. On /v2/positions it is also given for named star and radec targets. |
meta.conventions.label_origin | On /v2/positions, meta.conventions.label_origin: the place every row's tropical_sign and constellation were taken from, one value for the whole response, present when some row carries either label. It is geocentric, except that under frame=heliocentric the constellation is taken from the heliocentric place and label_origin says heliocentric, and no tropical_sign is emitted. Never topocentric, even when a row's position block is. A named star or radec row has a constellation and no tropical_sign. |
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.
| Class | What it means | Cache-Control (browser) | Netlify-CDN-Cache-Control (edge) |
|---|---|---|---|
fixed_instant | This request names a specific instant, past or future, so the answer can never change. Cached hard: a year, immutable. | public, max-age=31536000, immutable | public, durable, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400 |
now_relative | This 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=30 | public, durable, s-maxage=60, stale-while-revalidate=3600, stale-if-error=86400 |
aggregate | This 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=300 | public, durable, s-maxage=900, stale-while-revalidate=86400, stale-if-error=86400 |
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. | public, max-age=300 | public, 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
The discovery document: every endpoint, parameter, convention and limit.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
format | no | enum | json | Response format. This endpoint serves: json. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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.7.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 license 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, except where a pre-1972 offset has seconds, which ISO 8601 cannot write: those stay in UTC.",
"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/rise-set, /v2/sun, /v2/twilight and /v2/dark-window 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 for the free Basic tier, and a direct request is served at the anonymous tier whatever it sends. The paid tiers are sold through the RapidAPI listing and authenticate at that gateway; a key never belongs in a URL.",
"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": true
},
"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 license and what it obliges a caller to do",
"params": [
"endpoint?",
"fields?",
"format?",
"precision?",
"... 2 more rows omitted for length ..."
],
"formats": [
"json"
]
},
"... 27 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.
Every convention, threshold, model and limit the API uses, as data.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
format | no | enum | json | Response format. This endpoint serves: json. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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, so a time you give is read as UT1. UT1 minus UTC stays under 0.9 s, so that is the bound from 1972. Before 1972 the larger uncertainty is delta-T itself; see delta_t_description.",
"tt": "Terrestrial Time: UTC plus delta-T, with delta-T as delta_t_description states.",
"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 delta-T model, which departs from that figure by UT1 minus UTC (under 0.9 s) from 1972 to the end of the IERS prediction, and by more after it. /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 modeled tt reading.",
"delta_t_model": "IERS, Espenak-Meeus",
"delta_t_description": "TT minus UT1 from the IERS measurements (IERS EOP 20 C04 through 2025-05-05, then IERS Rapid Service finals through 2026-10-08) and prediction (IERS Bulletin A XXXIX No. 41, issued 2026-10-08, through 2027-10-08) from 1972, good to a few hundredths of a second there; the Espenak-Meeus polynomial before 1972; a linear blend back to that polynomial by 2040.",
"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."
},
"field_definitions": {
"tropical_sign": "The 30-degree sector of the body's apparent ecliptic longitude in the true ecliptic and equinox of date (frame ect), counted from the moving March equinox: Aries 0 to 30 deg, Taurus 30 to 60 deg, and so on to Pisces 330 to 360 deg. A named coordinate bin, not a constellation: because the equinox precesses, the bins drift against the stars by about 1 deg every 71.6 years, at the measured IAU 2006 precession rate. Taken from the geocentric place on /v2/positions, /v2/moon and /v2/planet-board, even when lat and lon make a position block topocentric: /v2/positions says so in meta.conventions.label_origin and /v2/planet-board in position.ecliptic_origin. With a location the Moon's topocentric longitude can sit up to about 1 deg from the geocentric one its sector is taken from. On /v2/sun, which always has a location, it is taken from the topocentric place, at most about 9 arcsec from the geocentric one. Emitted for Solar System bodies only.",
"constellation": "The IAU constellation containing the position: one of the 88 regions whose boundaries Delporte drew for the IAU in 1930 along lines of right ascension and declination of the B1875.0 equinox. The J2000 right ascension and declination are precessed to B1875.0 and looked up there. A body off the ecliptic can sit outside the zodiac constellations, so the constellation and tropical_sign often disagree: Saturn at 2026-10-04T12:00:00Z is at ecliptic longitude 11.30 deg and latitude -2.71 deg, in the Aries bin of tropical_sign and in the constellation Cetus. Taken from the geocentric place on /v2/positions, /v2/moon, /v2/today, /v2/retrogrades and /v2/planet-events, even when lat and lon make a position block topocentric, and on /v2/positions from the heliocentric place under frame=heliocentric. On /v2/planet-board (with a location, unless frames=minimal) and /v2/sun it is taken from the topocentric place, which sits at most the body's parallax from the geocentric one: about 9 arcsec for the Sun and 33 arcsec for Venus at its closest. On /v2/positions it is also given for named star and radec targets.",
"origin": "On /v2/positions, meta.conventions.label_origin: the place every row's tropical_sign and constellation were taken from, one value for the whole response, present when some row carries either label. It is geocentric, except that under frame=heliocentric the constellation is taken from the heliocentric place and label_origin says heliocentric, and no tropical_sign is emitted. Never topocentric, even when a row's position block is. A named star or radec row has a constellation and no tropical_sign."
},
"corrections": {
"aberration": {
"applied": true,
"note": "Applied to every apparent place, including named star and radec targets, which take annual aberration."
},
"light_travel_time": {
"applied": true,
"note": "Applied to the Sun and the planets. The Moon's place is geometric: its light time of about 1.3 s would move it by under 1 arcsec. A named star or radec target does not take it: a catalog place is already the direction its light arrives from."
},
"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. For a star it is far below a milliarcsecond."
},
"proper_motion": {
"applied": true,
"note": "Applied to named star targets (star: and hr:) from the Bright Star Catalog proper motion, parallax and radial velocity, as linear space motion from epoch J2000.0 to the requested instant. A caller-supplied radec target is read as a J2000 place and is not moved."
},
"polar_motion": {
"applied": false,
"note": "The effect is milliarcsecond class and is an explicit non-goal."
},
"light_deflection": {
"applied": false,
"note": "The bending of light by the Sun's gravity is not applied to any target. It is about 0.004 arcsec at 90 deg from the Sun and grows toward it: about 1 arcsec at half a degree, 1.75 arcsec at the solar limb."
}
},
"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": "center 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": "Center of the Sun 6 degrees below the horizon, ascending."
},
{
"code": "civil_dusk",
"body": "Sun",
"altitude_deg": -6,
"definition": "Center 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,
"moon_phase_law": "The Moon follows the Allen phase law in the form Krisciunas and Schaefer fitted (1991, Publications of the Astronomical Society of the Pacific 103, 1033): 0.026 mag per degree of Sun-Moon-Earth phase angle plus a 4e-9 quartic term. JPL Horizons reports the same curve. The lunar phase curve behind it was observed out to a phase angle of about 150 deg; beyond that, within about 2.4 days of new moon, the value is an extrapolation. It is kept and flagged: magnitude_extrapolated: true on a single-instant /v2/moon or /v2/positions answer and on /v2/phases rows, and true in the Moon slot of magnitudes_extrapolated on /v2/conjunctions. The rows of a /v2/moon or /v2/positions series carry no flag; the meta.conventions of a /v2/moon or /v2/positions answer carrying a Moon magnitude gives the threshold as moon_magnitude_observed_phase_angle_deg.",
"moon_magnitude_observed_phase_angle_deg": 150
},
"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",
"tiers_defined": [
"anonymous",
"pro",
"founding",
"ultra",
"... 2 more rows omitted for length ..."
],
"keyed_tier_reachable": true,
"keyed_tier_note": "The pro, ultra and mega tiers are sold through the RapidAPI listing, and a subscriber's requests arriving through that gateway are served at their plan's caps: Basic maps to anonymous, Pro, Ultra and Mega to their columns. Sending \"key\" as a query parameter is accepted, ignored and disclosed with a parameter_ignored warning; no query parameter changes a tier. 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,
"founding": 1830,
"ultra": 3660,
"professional": 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,
"founding": 366,
"ultra": 366,
"professional": 366,
"mega": 366,
"endpoints": [
"/v2/planet-board",
"/v2/cycles"
]
},
{
"shape": "subdaily_grid",
"unit": "days",
"anonymous": 31,
"pro": 93,
"founding": 93,
"ultra": 186,
"professional": 186,
"mega": 186,
"endpoints": [
"/v2/positions",
"/v2/time",
"/v2/sidereal-time"
]
},
{
"shape": "event_list",
"unit": "events",
"anonymous": 100,
"pro": 400,
"founding": 400,
"ultra": 500,
"professional": 500,
"mega": 500,
"endpoints": [
"/v2/seasons",
"/v2/apsides",
"/v2/moon-nodes",
"/v2/eclipses",
"... 6 more rows omitted for length ..."
]
},
"... 1 more row 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
},
"... 14 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.",
"range_boundaries": {
"rule": "The `end` of a range query does not mean the same thing on every endpoint. /v2 shipped three behaviours, each internally consistent, and keeps them: api/versioning.html promises /v2 keeps its present behavior for life, and changing any of them would delete or add a row for a caller who did not change their request.",
"meanings": {
"inclusive": "A row landing exactly on `end` is returned. The window is [start, end].",
"exclusive": "A row landing exactly on `end` is not returned. The window is [start, end).",
"day_start_before_end": "`end` bounds the start of a day rather than a row. Every local day that begins before `end` is returned, so the last row can begin before `end` and finish after it."
},
"endpoints": [
{
"path": "/v2/positions",
"end_boundary": "inclusive"
},
{
"path": "/v2/rise-set",
"end_boundary": "day_start_before_end"
},
{
"path": "/v2/sun",
"end_boundary": "day_start_before_end"
},
{
"path": "/v2/moon",
"end_boundary": "inclusive"
},
"... 17 more rows omitted for length ..."
]
},
"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/seasons",
"/v2/apsides",
"/v2/eclipses",
"/v2/phases"
]
},
"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(). Endpoints may narrow it further and several do; every narrowing is in endpoint_policies, and a BAD_STEP from an endpoint names durations that endpoint accepts.",
"endpoint_policies": [
{
"endpoint": "/v2/sun",
"minimum_minutes": 1,
"maximum_minutes": 525600,
"whole_days_only": true,
"accepted_values": null,
"accepted_examples": [
"1d",
"2d",
"7d",
"30d",
"... 1 more row omitted for length ..."
],
"note": "This endpoint returns one row per local day, so the stride has to be a whole number of days; for a monthly cadence send 30d."
},
{
"endpoint": "/v2/twilight",
"minimum_minutes": null,
"maximum_minutes": null,
"whole_days_only": false,
"accepted_values": [
"1d"
],
"accepted_examples": [
"1d"
],
"note": "It answers a per-day question and takes no other stride."
},
{
"endpoint": "/v2/conjunctions",
"minimum_minutes": 60,
"maximum_minutes": 10080,
"whole_days_only": false,
"accepted_values": null,
"accepted_examples": [
"1h",
"6h",
"1d",
"2d",
"... 1 more row omitted for length ..."
],
"note": null
},
{
"endpoint": "/v2/separation",
"minimum_minutes": 1,
"maximum_minutes": 10080,
"whole_days_only": false,
"accepted_values": null,
"accepted_examples": [
"1min",
"15min",
"6h",
"1d",
"... 1 more row omitted for length ..."
],
"note": null
},
"... 2 more rows omitted for length ..."
],
"endpoint_policies_note": "Every step-accepting endpoint not listed here takes the generic grammar unchanged. A row with accepted_values names the complete set; a row with only accepted_examples names a sample of a continuum bounded by minimum_minutes and maximum_minutes, or by endpoint_override where those are null.",
"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."
},
"... 13 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."
},
{
"endpoint": "/v2/rarity",
"max_engine_calls": 200000,
"note": "Enforced in v2-rarity.mjs. visible=any is checked pre-flight, before any astronomy runs: at the parameter maxima that walk costs about 27,000 calls, so no request reaches the cap. visible=dark is checked as it walks: a side that reaches the cap is null, flagged truncated and warned as unknown, and the sentence makes no claim for it. tools/sweep-rarity-budget.mjs runs 1,368 located requests (12 pairs at 19 latitudes from 89 N to 80 S on 6 dates); on 2026-09-24 none reached the cap and the costliest took 172,732 calls. It can be reached: Jupiter and Saturn 170 degrees apart seen from 66 S on 2030-06-01 reach it on the forward side."
}
]
},
"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.
Every enumerated value the API can emit or accept, as data.
The meteor_showers set is withheld until the meteor catalog reduction clears its license review.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
set | no | a set name, e.g. bodies, moon_phase_names | (every set) | Return just one enumerated set by name instead of the full catalog. An unrecognized name 400s and lists every real name in supported. |
endpoint | no | a /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. |
format | no | enum | json | Response format. This endpoint serves: json. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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.
Every source behind every endpoint, with its license and what it obliges a caller to do.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
endpoint | no | a /v2 path, e.g. /v2/sun | (every endpoint) | Return the source and license rows for just one endpoint instead of every source behind the API. |
fields | no | comma-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. |
format | no | enum | json | Response format. This endpoint serves: json. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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 license 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
Where every body is, in every frame, with refraction stated.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
step | no | duration: 1min, 1h, 6h, 1d, 7d, 1mo, 1y | endpoint-specific | The stride of a range query, from 1 minute to 1 year. Only meaningful together with start. |
count | no | integer | endpoint-specific | How many rows to return from start. Mutually exclusive with end. |
bodies | no | comma-separated body names, star:<name> or hr:<n> named stars and/or radec: targets, up to 20 | sun,moon,mercury,venus,mars,jupiter,saturn,uranus,neptune,pluto | Multiple targets. Mutually exclusive with body. Duplicates are collapsed with a warning, so hr:3982 and star:regulus count once. |
body | no | a body name, star:<name> or hr:<n> (one of 80 named stars, listed at /v2/enums?set=stars), or radec:<ra>,<dec>[,j2000] | (see bodies) | A single target. Mutually exclusive with bodies. A named star is moved to the requested instant by its catalog proper motion and carries its catalog figures in a star block. |
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
frames | no | minimal | standard | all | standard | How 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. |
frame | no | geocentric | heliocentric | geocentric | Which center to compute from. heliocentric excludes the Sun (its position relative to itself is undefined) and allows body=earth. |
refraction | no | normal | none | normal | normal 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_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | no | opaque 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.821354,
"degree_in_sign_whole": 5
},
"magnitude": -26.71,
"position": {
"equatorial": {
"j2000": {
"ra_hours": 8.520511,
"ra_string": "08h 31m 13.8s",
"dec_deg": 18.903245,
"dec_string": "+18 54 11.7"
},
"of_date": {
"ra_hours": 8.546074,
"ra_string": "08h 32m 45.9s",
"dec_deg": 18.813227,
"dec_string": "+18 48 47.6"
}
},
"ecliptic": {
"true_of_date": {
"longitude_deg": 125.820964,
"latitude_deg": -0.00227
}
},
"horizontal": {
"azimuth_deg": 322.385058,
"compass": "NW",
"altitude_refracted_deg": -11.261975,
"altitude_unrefracted_deg": -11.829877,
"refraction_deg": 0.567902,
"hour_angle_hours": 9.39122,
"parallactic_angle_deg": 23.67781
},
"distance_au": 1.015332,
"distance_km": 151891533.811791,
"distance_light_minutes": 8.44426,
"origin": "topocentric",
"observer_height_m": 11
},
"angular_diameter_arcsec": 1890.306904,
"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.844403,
"degree_in_sign_whole": 27
},
"magnitude": -12.37,
"position": {
"equatorial": {
"j2000": {
"ra_hours": 20.030381,
"ra_string": "20h 01m 49.4s",
"dec_deg": -24.232495,
"dec_string": "-24 13 57.0"
},
"of_date": {
"ra_hours": 20.057112,
"ra_string": "20h 03m 25.6s",
"dec_deg": -24.158391,
"dec_string": "-24 09 30.2"
}
},
"ecliptic": {
"true_of_date": {
"longitude_deg": 297.964976,
"latitude_deg": -3.666185
}
},
"horizontal": {
"azimuth_deg": 150.838906,
"compass": "SSE",
"altitude_refracted_deg": 9.464547,
"altitude_unrefracted_deg": 9.368919,
"refraction_deg": 0.095628,
"hour_angle_hours": 21.880181,
"parallactic_angle_deg": -19.427218
},
"distance_au": 0.002676,
"distance_km": 400282.267423,
"distance_light_minutes": 0.022253,
"origin": "topocentric",
"observer_height_m": 11
},
"position_geocentric": {
"equatorial": {
"j2000": {
"ra_hours": 20.008787,
"ra_string": "20h 00m 31.6s",
"dec_deg": -23.386841,
"dec_string": "-23 23 12.6"
},
"of_date": {
"ra_hours": 20.035378,
"ra_string": "20h 02m 07.4s",
"dec_deg": -23.313471,
"dec_string": "-23 18 48.5"
}
},
"ecliptic": {
"true_of_date": {
"longitude_deg": 297.844403,
"latitude_deg": -2.778232
}
},
"distance_au": 0.002683,
"distance_km": 401385.491484,
"distance_light_minutes": 0.022315,
"origin": "geocentric"
},
"parallax_deg": 0.896076,
"angular_diameter_arcsec": 1790.564453,
"angular_diameter_basis": "topocentric",
"elongation_from_sun_deg": 171.55606,
"ecliptic_separation_from_sun_deg": 172.023049,
"is_up": true,
"magnitude_extrapolated": false
}
]
},
"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"
}
Rise, transit, set and lower transit for any named object, with an explicit status at every latitude.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
body | yes | a body name, star:<name> or hr:<n> (one of 80 named stars, listed at /v2/enums?set=stars), 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. |
bodies | no | comma-separated body names, star:<name> or hr:<n> named stars and/or radec: targets, up to 20 | (required unless body is sent) | Multiple targets, each computed independently. Alternative to body. |
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
lat | yes | decimal degrees, -90 to 90 | (required) | Required; every rise/set event is defined relative to a horizon. |
lon | yes | decimal degrees, -180 to 180 | (required) | Required alongside lat. |
elevation_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
day_anchor | no | civil | solar | utc | civil if tz or place set a zone, else solar | What 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. |
horizon | no | degrees, -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_days | no | integer, 1 to 400 | 400 | How 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_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | no | opaque 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",
"weekday": {
"iso_weekday": 1,
"name": "Monday"
}
},
"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.340+01:00",
"azimuth_deg": 359.999969,
"altitude_deg": 1.903524
},
{
"kind": "upper_transit",
"instant": "2026-12-21T21:23:34.410+01:00",
"azimuth_deg": 179.999965,
"altitude_deg": 44.47149
}
],
"rise": [],
"set": [],
"transit": [
"2026-12-21T21:23:34.410+01:00"
],
"above_horizon_minutes": 1440,
"next_rise": "2026-12-26T16:11:26.122+01:00",
"next_set": "2026-12-26T12:40:46.755+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"
}
The complete solar day: every named boundary, azimuth, day length and polar status.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
lat | yes | decimal degrees, -90 to 90 | (required) | Required; every sunrise, sunset and twilight boundary is defined relative to a horizon. |
lon | yes | decimal degrees, -180 to 180 | (required) | Required alongside lat. |
elevation_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
step | no | duration: 1d, 2d, 7d, 30d, 1y | endpoint-specific | The stride of a range query, narrower on this endpoint than the generic 1 minute to 1 year. This endpoint returns one row per local day, so the stride has to be a whole number of days; for a monthly cadence send 30d. Only meaningful together with start. |
day_anchor | no | civil | solar | utc | civil if tz or place set a zone, else solar | What 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. |
depressions | no | comma-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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
time_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv, txt. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | no | opaque 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.968+01:00",
"azimuth_deg": 359.999871,
"altitude_deg": -18.760103
},
{
"kind": "rise",
"instant": "2026-07-27T05:16:24.053+01:00",
"azimuth_deg": 56.865506,
"altitude_deg": -0.833319
},
{
"kind": "upper_transit",
"instant": "2026-07-27T13:06:33.641+01:00",
"azimuth_deg": 179.999986,
"altitude_deg": 57.668931
},
{
"kind": "set",
"instant": "2026-07-27T20:55:46.275+01:00",
"azimuth_deg": 302.865705,
"altitude_deg": -0.833302
}
],
"rise": [
"2026-07-27T05:16:24.053+01:00"
],
"set": [
"2026-07-27T20:55:46.275+01:00"
],
"transit": [
"2026-07-27T13:06:33.641+01:00"
],
"above_horizon_minutes": 939.4
},
"solar_noon": "2026-07-27T13:06:33.641+01:00",
"solar_noon_altitude_deg": 57.668931,
"solar_noon_altitude_refracted_deg": 57.668931,
"solar_noon_altitude_unrefracted_deg": 57.658234,
"solar_noon_azimuth_deg": 179.999986,
"solar_midnight": "2026-07-27T01:06:33.968+01:00",
"solar_midnight_altitude_deg": -18.760103,
"solar_midnight_altitude_refracted_deg": -18.760103,
"solar_midnight_altitude_unrefracted_deg": -19.273925,
"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.804+01:00",
"dusk": "2026-07-27T21:37:19.101+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.807+01:00",
"dusk": "2026-07-27T22:34:05.860+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.059+01:00",
"dusk": "2026-07-28T00:00:00.620+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.712+01:00",
"dusk": "2026-07-27T23:40:16.028+01:00"
},
{
"depression_deg": 18,
"dawn": "2026-07-27T02:08:20.059+01:00",
"dusk": "2026-07-28T00:00:00.620+01:00"
}
],
"golden_hour": {
"morning": {
"begin": "2026-07-27T04:51:20.827+01:00",
"end": "2026-07-27T06:06:20.727+01:00"
},
"evening": {
"begin": "2026-07-27T20:06:02.103+01:00",
"end": "2026-07-27T21:20:41.026+01:00"
},
"definition": "Sun center from -4 degrees to +6 degrees."
},
"blue_hour": {
"morning": {
"begin": "2026-07-27T04:34:35.804+01:00",
"end": "2026-07-27T04:51:20.827+01:00"
},
"evening": {
"begin": "2026-07-27T21:20:41.026+01:00",
"end": "2026-07-27T21:37:19.101+01:00"
},
"definition": "Sun center from -6 degrees to -4 degrees."
},
"night_begins": "2026-07-28T00:00:00.620+01:00",
"night_ends": "2026-07-28T02:13:33.966+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.506487,
"compass": "NNW",
"altitude_refracted_deg": -17.250725,
"altitude_unrefracted_deg": -17.775433,
"refraction_deg": 0.524708,
"declination_of_date_deg": 19.258698,
"hour_angle_hours": 10.890579,
"shadow_length_multiplier": null,
"is_up": false
},
"tropical_sign": {
"name": "Leo",
"code": "leo",
"sign_index": 4,
"degree_in_sign": 3.970335,
"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"
}
The Moon in render-ready form: phase, illumination, bright limb, libration.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
step | no | duration: 1min, 1h, 6h, 1d, 7d, 1mo, 1y | endpoint-specific | The stride of a range query, from 1 minute to 1 year. Only meaningful together with start. |
count | no | integer | endpoint-specific | How many rows to return from start. Mutually exclusive with end. |
lat | no | decimal degrees, -90 to 90 | (none) | Optional. Supplying both lat and lon adds rise, transit and set times for the Moon. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
hemisphere | no | north | south | south if lat < 0, else north | Which 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_phases | no | integer, 0 to 24 | 4 | How many upcoming quarter phases (new, first quarter, full, last quarter) to list in data.next_phases. |
style | no | name | emoji | sentence | name | How the phase is labeled in the txt format's one-line summary. Has no effect on the JSON field names, which are always present. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
day_anchor | no | civil | solar | utc | civil if tz or place set a zone, else solar | What 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_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv, txt. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | no | opaque 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.701388,
"phase_angle_convention": "Apparent ecliptic longitude of the Moon minus the Sun's, as /v2/positions reports them, 0 to 360 degrees: 0 at new moon, 180 at full moon.",
"illumination_phase_angle_deg": 19.583557,
"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.971077,
"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.213632,
"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.63163,
"orientation_angle_deg": 266.845262,
"orientation_definition": "position_angle_deg minus parallactic_angle_deg, normalized 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.781575,
"distance_au": 0.002691,
"distance_basis": "topocentric",
"angular_diameter_arcsec": 1780.531436,
"fraction_of_mean_distance": 1.049656,
"fraction_of_mean_distance_definition": "The geocentric Earth-Moon center 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.393486,
"latitude_deg": 4.8144,
"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 disk 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.547339,
"degree_in_sign_whole": 15
},
"constellation": {
"name": "Sagittarius",
"abbreviation": "Sgr"
},
"position_geocentric": {
"equatorial": {
"j2000": {
"ra_hours": 19.128396,
"ra_string": "19h 07m 42.2s",
"dec_deg": -26.20588,
"dec_string": "-26 12 21.2"
},
"of_date": {
"ra_hours": 19.155938,
"ra_string": "19h 09m 21.4s",
"dec_deg": -26.164053,
"dec_string": "-26 09 50.6"
}
},
"ecliptic": {
"true_of_date": {
"longitude_deg": 285.547339,
"latitude_deg": -3.657035
}
},
"distance_au": 0.002697,
"distance_km": 403487.958468,
"distance_light_minutes": 0.022432,
"origin": "geocentric"
},
"position_topocentric": {
"equatorial": {
"j2000": {
"ra_hours": 19.147333,
"ra_string": "19h 08m 50.4s",
"dec_deg": -27.063885,
"dec_string": "-27 03 50.0"
},
"of_date": {
"ra_hours": 19.175046,
"ra_string": "19h 10m 30.2s",
"dec_deg": -27.021347,
"dec_string": "-27 01 16.8"
}
},
"ecliptic": {
"true_of_date": {
"longitude_deg": 285.699481,
"latitude_deg": -4.538859
}
},
"horizontal": {
"azimuth_deg": 155.833709,
"compass": "SSE",
"altitude_refracted_deg": 8.0767,
"altitude_unrefracted_deg": 7.966301,
"refraction_deg": 0.1104,
"hour_angle_hours": 22.195167,
"parallactic_angle_deg": -16.63163
},
"distance_au": 0.002691,
"distance_km": 402537.781575,
"distance_light_minutes": 0.022379,
"origin": "topocentric",
"observer_height_m": 0
},
"parallax_deg": 0.894786,
"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.372+01:00",
"azimuth_deg": 221.893771,
"altitude_deg": -0.812725
},
{
"kind": "lower_transit",
"instant": "2026-07-27T11:25:36.265+01:00",
"azimuth_deg": 359.999924,
"altitude_deg": -65.724579
},
{
"kind": "rise",
"instant": "2026-07-27T20:18:10.111+01:00",
"azimuth_deg": 135.541538,
"altitude_deg": -0.813389
},
{
"kind": "upper_transit",
"instant": "2026-07-27T23:50:52.805+01:00",
"azimuth_deg": 179.999798,
"altitude_deg": 11.724039
}
],
"rise": [
"2026-07-27T20:18:10.111+01:00"
],
"set": [
"2026-07-27T02:22:25.372+01:00"
],
"transit": [
"2026-07-27T23:50:52.805+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:35:42.204+01:00",
"days_until": 1.73
},
{
"code": "last_quarter",
"name": "Last Quarter",
"instant": "2026-08-06T03:21:27.594+01:00",
"days_until": 9.22
},
{
"code": "new_moon",
"name": "New Moon",
"instant": "2026-08-12T18:36:41.553+01:00",
"days_until": 15.86
},
{
"code": "first_quarter",
"name": "First Quarter",
"instant": "2026-08-20T03:46:20.653+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.",
"magnitude_extrapolated": false
},
"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"
}
Every time scale, sidereal time, solar time, the equation of time and the Julian dates.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
step | no | duration: 1min, 1h, 6h, 1d, 7d, 1mo, 1y | endpoint-specific | The stride of a range query, from 1 minute to 1 year. Only meaningful together with start. |
count | no | integer | endpoint-specific | How many rows to return from start. Mutually exclusive with end. |
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
time_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | no | opaque 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. UT1 minus UTC stays under 0.9 s, the bound on this reading.",
"tai": "2026-07-27T12:00:37.000",
"tt": "2026-07-27T12:01:09.171",
"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": 69.17145,
"delta_t_model": "IERS, Espenak-Meeus",
"tai_minus_utc_seconds": 37,
"tai_source": "leap_second_table",
"tt_source": "engine_delta_t_model",
"tt_minus_tai_seconds": 32.17145,
"tt_minus_tai_definition_seconds": 32.184,
"delta_t_model_residual_seconds": -0.013,
"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.0008005956,
"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.5751,
"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.890649,
"apparent_solar_time_string": "11:53:26",
"equation_of_time_minutes": -6.555071,
"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.139138,
"sun_greenwich_hour_angle_deg": 358.361232,
"sun_local_hour_angle_deg": 358.359732,
"local_apparent_noon": "2026-07-27T12:06:33.641Z"
},
"weekday": {
"local": {
"date": "2026-07-27",
"iso_weekday": 1,
"name": "Monday"
},
"utc": {
"date": "2026-07-27",
"iso_weekday": 1,
"name": "Monday"
}
}
},
"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"
}
The whole sky for a place right now, in one request.
Meteor-shower events are withheld from next_events until the meteor catalog reduction clears its license review.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
day_anchor | no | civil | solar | utc | civil if tz or place set a zone, else solar | What 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. |
include | no | comma-separated: sun, moon, planets, events, night | all five | Which sections to include in data. Sending include= (empty) is a 400; omit the parameter for the full set. |
time_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, txt. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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.104+01:00",
"azimuth_deg": 359.999874,
"altitude_deg": -18.989519
},
{
"kind": "rise",
"instant": "2026-07-28T05:17:50.623+01:00",
"azimuth_deg": 57.279319,
"altitude_deg": -0.833326
},
{
"kind": "upper_transit",
"instant": "2026-07-28T13:06:32.472+01:00",
"azimuth_deg": 179.999991,
"altitude_deg": 57.43864
},
{
"kind": "set",
"instant": "2026-07-28T20:54:16.599+01:00",
"azimuth_deg": 302.447494,
"altitude_deg": -0.833302
}
],
"rise": [
"2026-07-28T05:17:50.623+01:00"
],
"set": [
"2026-07-28T20:54:16.599+01:00"
],
"transit": [
"2026-07-28T13:06:32.472+01:00"
],
"above_horizon_minutes": 936.4
},
"day_length": "15h 36m",
"day_length_minutes": 936.4,
"civil_dusk": "2026-07-28T21:35:35.041+01:00",
"astronomical_dusk": "2026-07-28T23:55:02.020+01:00",
"is_up": false,
"altitude_refracted_deg": -8.179048,
"azimuth_deg": 315.874249
},
"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:08.878+01:00",
"azimuth_deg": 225.852634,
"altitude_deg": -0.813717
},
{
"kind": "lower_transit",
"instant": "2026-07-28T12:15:44.515+01:00",
"azimuth_deg": 359.999939,
"altitude_deg": -63.380249
},
{
"kind": "rise",
"instant": "2026-07-28T20:46:05.635+01:00",
"azimuth_deg": 130.062784,
"altitude_deg": -0.814654
}
],
"rise": [
"2026-07-28T20:46:05.635+01:00"
],
"set": [
"2026-07-28T03:29:08.878+01:00"
],
"transit": [],
"above_horizon_minutes": 403.1
},
"is_up": true,
"altitude_refracted_deg": 7.009083,
"azimuth_deg": 144.577525,
"next_full_moon": "2026-07-29T15:35:42.204+01:00",
"next_new_moon": "2026-08-12T18:36:41.553+01:00"
},
"planets_up": [
{
"name": "Venus",
"code": "venus",
"constellation": {
"name": "Leo",
"abbreviation": "Leo"
},
"magnitude": -4.29,
"altitude_refracted_deg": 3.308559,
"azimuth_deg": 272.336498,
"elongation_from_sun_deg": 44.992296,
"visibility": "Evening sky, after dusk",
"visibility_code": "evening_sky",
"naked_eye": true,
"altitude_at_civil_dusk_deg": 7.017111,
"altitude_at_civil_dawn_deg": -33.802555,
"observable_tonight": true
},
{
"name": "Pluto",
"code": "pluto",
"constellation": {
"name": "Capricornus",
"abbreviation": "Cap"
},
"magnitude": 14.45,
"altitude_refracted_deg": 5.107099,
"azimuth_deg": 138.35641,
"elongation_from_sun_deg": 175.452418,
"visibility": "Up most of the night",
"visibility_code": "most_of_night",
"naked_eye": false,
"altitude_at_civil_dusk_deg": 2.554747,
"altitude_at_civil_dawn_deg": 1.968705,
"observable_tonight": true
}
],
"planets_down": [
"Mercury",
"Mars",
"Jupiter",
"Saturn",
"... 2 more rows omitted for length ..."
],
"planets_tonight": [
{
"name": "Venus",
"code": "venus",
"constellation": {
"name": "Leo",
"abbreviation": "Leo"
},
"magnitude": -4.29,
"naked_eye": true,
"elongation_from_sun_deg": 44.992296,
"visibility": "Evening sky, after dusk",
"visibility_code": "evening_sky",
"altitude_at_civil_dusk_deg": 7.017111,
"altitude_at_civil_dawn_deg": -33.802555
},
{
"name": "Saturn",
"code": "saturn",
"constellation": {
"name": "Pisces",
"abbreviation": "Psc"
},
"magnitude": 0.53,
"naked_eye": true,
"elongation_from_sun_deg": 111.036487,
"visibility": "Morning sky, before dawn",
"visibility_code": "morning_sky",
"altitude_at_civil_dusk_deg": -14.223194,
"altitude_at_civil_dawn_deg": 40.771537
},
{
"name": "Mercury",
"code": "mercury",
"constellation": {
"name": "Gemini",
"abbreviation": "Gem"
},
"magnitude": 0.87,
"naked_eye": true,
"elongation_from_sun_deg": 18.527183,
"visibility": "Morning sky, before dawn",
"visibility_code": "morning_sky",
"altitude_at_civil_dusk_deg": -12.897916,
"altitude_at_civil_dawn_deg": 4.999245
},
{
"name": "Mars",
"code": "mars",
"constellation": {
"name": "Taurus",
"abbreviation": "Tau"
},
"magnitude": 1.31,
"naked_eye": true,
"elongation_from_sun_deg": 44.85049,
"visibility": "Morning sky, before dawn",
"visibility_code": "morning_sky",
"altitude_at_civil_dusk_deg": -14.686134,
"altitude_at_civil_dawn_deg": 24.676685
},
"... 3 more rows omitted for length ..."
],
"planets_tonight_basis": {
"civil_dusk": "2026-07-28T21:35:35.041+01:00",
"civil_dawn": "2026-07-29T04:37:58.701+01:00",
"sun_altitude_deg": -6,
"night": "normal",
"definition": "Listed when above the horizon at the twilight its elongation puts it in, sampled at these two instants; the rule behind each visibility_code is published at https://www.cyclecalcs.com/v2/enums?set=visibility_phrases."
},
"night": {
"dark_start": "2026-07-28T23:55:02.020+01:00",
"dark_end": "2026-07-29T02:18:30.182+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:35:42.204+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:36:41.553+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:53.048+01:00",
"days_until": 14.9,
"scope": "global",
"href": "https://www.cyclecalcs.com/eclipses/",
"global_kind": "total",
"hybrid": false
},
{
"kind": "lunar_eclipse",
"label": "Partial lunar eclipse",
"instant": "2026-08-28T05:12:55.353+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 is in the evening sky after dusk. Saturn, Mercury, Mars and Uranus are in the morning sky before dawn. 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
Every equinox and solstice in a window, with the length of each season they bound, and on request the cross-quarters and all 24 solar terms.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
count | no | integer | endpoint-specific | How many rows to return from start. Mutually exclusive with end. |
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
hemisphere | no | north | south | north | Selects 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. |
include | no | comma-separated: events, seasons, cross_quarters, year_length, solar_terms | events, seasons | Which sections to add to the response. events lists each equinox/solstice; seasons adds the season each one opens with its length; cross_quarters adds to events the four instants the Sun reaches apparent longitude 45, 135, 225 and 315 deg; year_length adds the tropical-year figure. solar_terms adds to events every instant the Sun's apparent longitude reaches a multiple of 15 deg, 24 a year (the Hong Kong Observatory definition of the solar terms): the quarters and cross-quarters keep their kinds and the other sixteen are kind solar_term, each named by its solar_longitude_deg alone. solar_terms brings events and cross_quarters with it. |
time_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | no | opaque 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:40.060+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:14.551+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:46.692+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:20.387+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:40.060+00:00",
"end": "2026-06-21T09:25:14.551+01:00",
"length_days": 92.7358,
"ends_inside_window": true
},
{
"name": "Summer",
"name_neutral": "June solstice season",
"starts_with": "solstice_june",
"ends_with": "equinox_september",
"start": "2026-06-21T09:25:14.551+01:00",
"end": "2026-09-23T01:05:46.692+01:00",
"length_days": 93.6531,
"ends_inside_window": true
},
{
"name": "Autumn",
"name_neutral": "September equinox season",
"starts_with": "equinox_september",
"ends_with": "solstice_december",
"start": "2026-09-23T01:05:46.692+01:00",
"end": "2026-12-21T20:50:20.387+00:00",
"length_days": 89.8643,
"ends_inside_window": true
},
{
"name": "Winter",
"name_neutral": "December solstice season",
"starts_with": "solstice_december",
"ends_with": "equinox_march",
"start": "2026-12-21T20:50:20.387+00:00",
"end": "2027-03-20T20:24:47.804+00:00",
"length_days": 88.9823,
"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.6708,
"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"
}
Every perigee, apogee, perihelion and aphelion in a window, with its distance.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
count | no | integer | endpoint-specific | How many rows to return from start. Mutually exclusive with end. |
body | no | moon | 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. |
bodies | no | comma-separated: moon, earth | moon | Multiple targets. moon returns perigee/apogee (distance from Earth); earth returns perihelion/aphelion (distance from the Sun). |
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
kind | no | near | far | all | all | near returns only the near apsis of each target (perigee or perihelion); far only the far one (apogee or aphelion); all returns both. |
include | no | comma-separated: events, extremes, nearest_syzygy, angular_size | events | events 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_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | no | opaque 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:31.471Z",
"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:15.346Z",
"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:47:01.554Z",
"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:56.074Z",
"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.
Every crossing of the ecliptic by the Moon, the node longitudes and the eclipse seasons they define.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
count | no | integer | endpoint-specific | How many rows to return from start. Mutually exclusive with end. |
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
node_model | no | true | mean | both | true | true 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. |
kind | no | ascending | descending | all | all | Filters to crossings heading north across the ecliptic (ascending), south (descending), or both. |
include | no | comma-separated: crossings, node_longitude, eclipse_seasons, periods, standstill | crossings, node_longitude | crossings 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_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | no | opaque 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:47.949Z",
"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:13.945Z",
"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:22.082Z",
"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:41.989Z",
"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.169211,
"mean_descending_deg": 162.169211,
"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.
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 catalog data, and the obvious source has not cleared a provenance check. The series number is closed-form and is published.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
count | no | whole number, 1 to 50 | 1 | How many eclipses of EACH type to return with direction=next or previous, so count=3 with type=both returns up to six. Refused with end; ignored, with a parameter_ignored warning, in a range. |
direction | no | next | previous | range | next (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. |
type | no | solar | lunar | both | both | Restricts the search to one kind of eclipse. |
lat | no | decimal degrees, -90 to 90 | (none) | Optional. Adds local circumstances (magnitude, obscuration, contact times as seen from here) and, with visible_only, filters the results. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
visible_only | no | true | false | false | Filters the results to eclipses visible from the given location. Requires lat and lon; sending it without a location is 400 LOCATION_REQUIRED. |
include | no | comma-separated: global, local, saros, contacts, greatest, path | global, saros (plus local, contacts automatically once a location is given) | A list that is sent replaces the default set. 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 circumstances at greatest local eclipse to the local block of a solar eclipse, so it needs a location and local. path adds the precomputed central path as inline GeoJSON (central line, both limits, per-minute circumstances) for the central solar eclipses that have one, and says why for every other eclipse; the geometry is left out, with a note, under format=csv or shape=flat or where it would push the response past the size limit. A token that cannot apply is ignored with a parameter_ignored warning. Tokens are accepted in any letter case, with spaces around the commas. |
time_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv, txt. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | no | opaque 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:53.048Z",
"peak_precision_seconds": 1,
"days_from_reference": 14.844364,
"href": "https://www.cyclecalcs.com/eclipses/",
"gamma": 0.897578,
"gamma_definition": "The perpendicular distance from the Earth's center to the shadow axis, in Earth radii, positive when the axis passes north of the center. 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.275593,
"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 catalog data. The NASA GSFC five-millennium catalog 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 license 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.92451,
"obscuration_fraction": 0.912906,
"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:24.865Z",
"altitude_deg": 18.959773,
"azimuth_deg": 270.191754,
"above_horizon": true
},
{
"kind": "maximum",
"instant": "2026-08-12T18:13:23.035Z",
"altitude_deg": 10.328722,
"azimuth_deg": 280.933754,
"above_horizon": true
},
{
"kind": "fourth_contact",
"instant": "2026-08-12T19:06:21.272Z",
"altitude_deg": 2.574991,
"azimuth_deg": 291.056407,
"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.3 deg above the horizon.",
"detectable": true,
"maximum_instant": "2026-08-12T18:13:23.035Z"
},
"peak_definition": "Greatest eclipse for the whole Earth, shown in your time zone. It is not when the eclipse peaks where you are: that is local.maximum_instant.",
"hybrid": false,
"hybrid_transitions": null,
"greatest_eclipse_duration_seconds": 138.210958,
"greatest_eclipse_path_width_km": 292.821209,
"greatest_eclipse_sun_altitude_deg": 25.814344
},
{
"type": "lunar",
"kind": "partial",
"label": "Lunar eclipse (partial)",
"peak": "2026-08-28T04:12:55.353Z",
"peak_precision_seconds": 1,
"days_from_reference": 30.279807,
"href": "https://www.cyclecalcs.com/eclipses/",
"durations": {
"penumbral_minutes": 338.435805,
"partial_minutes": 198.842995,
"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 disk 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:42.279Z"
},
{
"kind": "partial_begin",
"instant": "2026-08-28T02:33:30.063Z"
},
{
"kind": "maximum",
"instant": "2026-08-28T04:12:55.353Z"
},
{
"kind": "partial_end",
"instant": "2026-08-28T05:52:20.643Z"
},
"... 1 more row omitted for length ..."
],
"semiduration_penumbral_minutes": 169.217902,
"semiduration_partial_minutes": 99.421498,
"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 catalog data. The NASA GSFC five-millennium catalog 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 license 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.903834,
"moon_azimuth_at_maximum_deg": 242.929872,
"visible_contacts": [
"penumbral_begin",
"partial_begin",
"maximum"
],
"contacts": [
{
"kind": "penumbral_begin",
"instant": "2026-08-28T01:23:42.279Z",
"altitude_deg": 24.849812,
"azimuth_deg": 204.223409,
"above_horizon": true
},
{
"kind": "partial_begin",
"instant": "2026-08-28T02:33:30.063Z",
"altitude_deg": 19.373916,
"azimuth_deg": 221.365991,
"above_horizon": true
},
{
"kind": "maximum",
"instant": "2026-08-28T04:12:55.353Z",
"altitude_deg": 7.903834,
"azimuth_deg": 242.929872,
"above_horizon": true
},
{
"kind": "partial_end",
"instant": "2026-08-28T05:52:20.643Z",
"altitude_deg": -5.561709,
"azimuth_deg": 262.285829,
"above_horizon": false
},
"... 1 more row omitted for length ..."
],
"moonset_during_eclipse": "2026-08-28T05:15:24.422Z",
"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:39.357Z",
"peak_precision_seconds": 1,
"days_from_reference": 192.770594,
"href": "https://www.cyclecalcs.com/eclipses/",
"gamma": -0.294995,
"gamma_definition": "The perpendicular distance from the Earth's center to the shadow axis, in Earth radii, positive when the axis passes north of the center. 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.491836,
"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 catalog data. The NASA GSFC five-millennium catalog 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 license 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."
},
"hybrid": false,
"hybrid_transitions": null,
"greatest_eclipse_duration_seconds": 470.798976,
"greatest_eclipse_path_width_km": 281.282124,
"greatest_eclipse_sun_altitude_deg": 72.679548
},
{
"type": "lunar",
"kind": "penumbral",
"label": "Lunar eclipse (penumbral)",
"peak": "2027-02-20T23:12:50.552Z",
"peak_precision_seconds": 1,
"days_from_reference": 207.071418,
"href": "https://www.cyclecalcs.com/eclipses/",
"durations": {
"penumbral_minutes": 241.734405,
"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 disk 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:58.520Z"
},
{
"kind": "maximum",
"instant": "2027-02-20T23:12:50.552Z"
},
{
"kind": "penumbral_end",
"instant": "2027-02-21T01:13:42.584Z"
}
],
"semiduration_penumbral_minutes": 120.867203,
"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 catalog data. The NASA GSFC five-millennium catalog 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 license 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.921364,
"moon_azimuth_at_maximum_deg": 158.613283,
"visible_contacts": [
"penumbral_begin",
"maximum",
"penumbral_end"
],
"contacts": [
{
"kind": "penumbral_begin",
"instant": "2027-02-20T21:11:58.520Z",
"altitude_deg": 34.780772,
"azimuth_deg": 122.996985,
"above_horizon": true
},
{
"kind": "maximum",
"instant": "2027-02-20T23:12:50.552Z",
"altitude_deg": 45.921364,
"azimuth_deg": 158.613283,
"above_horizon": true
},
{
"kind": "penumbral_end",
"instant": "2027-02-21T01:13:42.584Z",
"altitude_deg": 45.447951,
"azimuth_deg": 200.8105,
"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:23.035Z",
"kind": "partial",
"altitude_deg": 10.328722,
"maximum_above_horizon": true,
"obscuration_fraction": 0.912906,
"eclipse": null
},
"lunar": {
"same_as_next_global": true,
"peak": "2026-08-28T04:12:55.353Z",
"kind": "partial",
"altitude_deg": 7.903833
}
},
"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.",
"central_geometry_note": "For every solar eclipse of 1700 to 2200, the contact times in include=local, include=path and the hybrid, hybrid_transitions, greatest_eclipse_duration_seconds, greatest_eclipse_path_width_km and greatest_eclipse_sun_altitude_deg fields come from NASA's Besselian elements for that eclipse (Five Millennium Canon of Solar Eclipses; Eclipse Predictions by Fred Espenak, NASA's GSFC), evaluated at the IERS delta-T. They use the canon's lunar radii, k2 = 0.272281 Earth equatorial radii (1736.6 km) for totality, the ring, the path and obscuration (so the Sun is 100 percent covered exactly from second contact), and k1 = 0.272488 for first and last contact, on the WGS84 ellipsoid. Two partial eclipses that no place sees with the Sun up, 1718-04-01 and 1787-07-14, have no elements on NASA's pages and keep the engine's own geometry. The greatest_eclipse fields are taken at the engine's point of greatest eclipse, which can sit about 2 km from the canon's; the path width is the width across the track of the band the shadow sweeps, and the Sun altitude is refracted, as in the local blocks. kind is the engine's, classified at greatest eclipse with its polar lunar radius of 1736.0 km, so within a few hundredths of a percent of magnitude 1 the k2 central phase behind the greatest_eclipse fields can be total where kind says annular; hybrid is true only where the k2 state at greatest eclipse agrees with kind.",
"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.
The lunar phase calendar: every quarter with its distance, apparent size, supermoon classification under BOTH competing rules, traditional name and any eclipse.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
count | no | integer | endpoint-specific | How many rows to return from start. Mutually exclusive with end. |
phases | no | comma-separated: new_moon, first_quarter, full_moon, last_quarter | all four | Restricts the calendar to specific quarters. |
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
day_anchor | no | civil | solar | utc | civil if tz or place set a zone, else solar | What 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. |
include | no | comma-separated: distance, classification, names, eclipse, libration, rise_set | distance, classification, names, eclipse | distance 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_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv, txt. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | no | opaque 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:35:42.204+01:00",
"instant_precision_seconds": 1,
"local_date": "2026-07-29",
"days_from_query": 0.649794,
"lunation_number": 1281,
"distance_km": 399606.805099,
"angular_diameter_arcsec": 1793.591094,
"fraction_of_mean_distance": 1.03956,
"apparent_size_vs_mean_percent": -3.805442,
"apparent_brightness_vs_mean_percent": -7.46607,
"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 its relative distance, (apogee - distance) / (apogee - perigee) for the apogee and perigee nearest in time, is 0.9 or more. The line is drawn for each orbit, so threshold_km moves from one orbit to the next.",
"qualifies": false,
"threshold_km": 367499.417229,
"threshold_year": null,
"closest_perigee_of_year_km": null,
"farthest_apogee_of_year_km": null,
"relative_distance": 0.140324,
"orbit_perigee_km": 363272.9585,
"orbit_apogee_km": 405537.545797
},
"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 popularized 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",
"magnitude_extrapolated": false
},
{
"kind": "last_quarter",
"label": "Last Quarter",
"instant": "2026-08-06T03:21:27.594+01:00",
"instant_precision_seconds": 1,
"local_date": "2026-08-06",
"days_from_query": 8.139903,
"lunation_number": 1281,
"distance_km": 373336.59946,
"angular_diameter_arcsec": 1919.799818,
"fraction_of_mean_distance": 0.971219,
"apparent_size_vs_mean_percent": 2.963385,
"apparent_brightness_vs_mean_percent": 6.014586,
"illuminated_fraction": 0.501279,
"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",
"magnitude_extrapolated": false
},
{
"kind": "new_moon",
"label": "New Moon",
"instant": "2026-08-12T18:36:41.553+01:00",
"instant_precision_seconds": 1,
"local_date": "2026-08-12",
"days_from_query": 14.775481,
"lunation_number": 1282,
"distance_km": 366944.060682,
"angular_diameter_arcsec": 1953.244932,
"fraction_of_mean_distance": 0.954589,
"apparent_size_vs_mean_percent": 4.757112,
"apparent_brightness_vs_mean_percent": 9.740525,
"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 its relative distance, (apogee - distance) / (apogee - perigee) for the apogee and perigee nearest in time, is 0.9 or more. The line is drawn for each orbit, so threshold_km moves from one orbit to the next.",
"qualifies": true,
"threshold_km": 367408.863864,
"threshold_year": null,
"closest_perigee_of_year_km": null,
"farthest_apogee_of_year_km": null,
"relative_distance": 0.911238,
"orbit_perigee_km": 363272.9585,
"orbit_apogee_km": 404632.012141
},
"criteria_disagree": true,
"disagreement_note": "Espenak's rule counts this as a supermoon and the fixed 360,000 km line does not: it is 6,944 km beyond the fixed line but at a relative distance of 0.911 in its own orbit, above Espenak's 0.9. Published lists that follow the two rules will disagree about this event."
},
"eclipse": {
"type": "solar",
"kind": "total",
"peak": "2026-08-12T18:45:53.048+01:00",
"minutes_from_phase_instant": 9.191583,
"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.",
"hybrid": false
},
"href": "https://www.cyclecalcs.com/full-moon-calendar.html",
"magnitude_extrapolated": true
},
{
"kind": "first_quarter",
"label": "First Quarter",
"instant": "2026-08-20T03:46:20.653+01:00",
"instant_precision_seconds": 1,
"local_date": "2026-08-20",
"days_from_query": 22.157183,
"lunation_number": 1282,
"distance_km": 401560.050988,
"angular_diameter_arcsec": 1784.866754,
"fraction_of_mean_distance": 1.044641,
"apparent_size_vs_mean_percent": -4.273346,
"apparent_brightness_vs_mean_percent": -8.364077,
"illuminated_fraction": 0.501277,
"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",
"magnitude_extrapolated": false
},
"... 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.
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.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
step | no | duration: 1min, 1h, 6h, 1d, 7d, 1mo, 1y | endpoint-specific | The stride of a range query, from 1 minute to 1 year. Only meaningful together with start. |
count | no | integer | endpoint-specific | How many rows to return from start. Mutually exclusive with end. |
keys | no | comma-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. |
kind | no | comma-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. |
family | no | comma-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. |
include | no | comma-separated: fraction, next_event, observed, definition | fraction, next_event, observed | fraction 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
time_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv, txt. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page 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.477842,
"percent_complete": 47.78418,
"fraction_method": "Apparent ecliptic longitude of the Moon minus the Sun's, as /v2/positions reports them, divided by 360: 0 at new moon, 0.5 at full.",
"position_label": "Full Moon, day 14 of 29.53",
"position_values": {
"phase_angle_deg": 172.023049,
"day_of_cycle": 14,
"phase_code": "full_moon"
},
"next_event": {
"kind": "full_moon",
"label": "Full Moon",
"instant": "2026-07-29T14:35:42.204Z",
"days_until": 0.712294
},
"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.826307,
"percent_complete": 82.630683,
"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.4705 deg J2000 ecliptic longitude",
"position_values": {
"ecliptic_longitude_j2000_deg": 297.47046
},
"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.553119,
"percent_complete": 55.311898,
"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:55.505Z",
"next_perigee": "2026-08-10T11:18:10.779Z",
"previous_perigee_distance_km": 359102.352973,
"interval_days": 28.13976
},
"next_event": {
"kind": "perigee",
"label": "The Moon reaches perigee",
"instant": "2026-08-10T11:18:10.779Z",
"days_until": 12.575125
},
"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.904324,
"percent_complete": 90.432431,
"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:30.175Z",
"next_ascending_node": "2026-07-31T11:53:04.779Z",
"interval_days": 27.168456
},
"next_event": {
"kind": "node_ascending",
"label": "The Moon crosses the ecliptic going north",
"instant": "2026-07-31T11:53:04.779Z",
"days_until": 2.599361
},
"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
All eight planets in one request: place, brightness, apparent size, retrograde state, next station and whether it is worth looking tonight.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
step | no | duration: 1min, 1h, 6h, 1d, 7d, 1mo, 1y | endpoint-specific | The stride of a range query, from 1 minute to 1 year. Only meaningful together with start. |
count | no | integer | endpoint-specific | How many rows to return from start. Mutually exclusive with end. |
bodies | no | comma-separated planet names, up to 20 | mercury,venus,mars,jupiter,saturn,uranus,neptune,pluto | Which planets to include. The Sun, Moon and Earth are not on the board; use /v2/positions for those. |
body | no | a planet name (mercury to pluto) | (see bodies) | A single planet, to filter the board to one row. Mutually exclusive with bodies. |
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
day_anchor | no | civil | solar | utc | civil if tz or place set a zone, else solar | What 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. |
include | no | comma-separated: position, visibility, motion, rise_set, physical, heliocentric | position, visibility, motion, rise_set | position 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-centered position. |
sort | no | distance_from_sun | magnitude | elongation | altitude | distance_from_sun | How the returned rows are ordered. altitude requires a location. |
refraction | no | normal | none | normal | normal 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. |
frames | no | minimal | standard | all | standard | How 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_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv, txt. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | no | opaque 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.",
"twilight_basis": {
"civil_dusk": "2026-07-28T21:35:35.041+01:00",
"civil_dawn": "2026-07-28T04:36:16.867+01:00",
"sun_altitude_deg": -6,
"never_dark": false,
"glare_release_altitude_deg": 5,
"glare_rule_overridden_definition": "bodies[].twilight.glare_rule_overridden is true when the elongation rule alone put the planet in the glare but it stands at least glare_release_altitude_deg up at this civil dusk or dawn, so the verdict was decided by this horizon instead. false means the elongation rule was not overridden; the verdict stands as visibility.code says."
},
"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.512634,
"degree_in_sign_whole": 17
},
"position": {
"equatorial": {
"j2000": {
"ra_hours": 7.212121,
"ra_string": "07h 12m 43.6s",
"dec_deg": 19.413258,
"dec_string": "+19 24 47.7"
},
"of_date": {
"ra_hours": 7.238313,
"ra_string": "07h 14m 17.9s",
"dec_deg": 19.368337,
"dec_string": "+19 22 06.0"
}
},
"ecliptic": {
"true_of_date": {
"longitude_deg": 107.512634,
"latitude_deg": -2.944596
}
},
"horizontal": {
"azimuth_deg": 340.745655,
"compass": "NNW",
"altitude_refracted_deg": -16.589178,
"altitude_unrefracted_deg": -17.118658,
"refraction_deg": 0.529479,
"hour_angle_hours": 10.69898,
"parallactic_angle_deg": 12.574122
},
"distance_au": 0.77774,
"distance_km": 116348263.88084,
"distance_light_minutes": 6.468267,
"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 center 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.65031,
"phase_angle_deg": 119.788465,
"illuminated_fraction": 0.2516,
"illuminated_percent": 25.160037,
"ring_tilt_deg": null
},
"visibility": {
"elongation_deg": 18.535991,
"sun_side": "morning",
"ecliptic_separation_deg": 18.308721,
"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.489661,
"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:27.295+01:00",
"instant_precision_seconds": 2,
"days_from_query": 87.406566
}
},
"rise_set": {
"status": "normal",
"status_label": "Rises and sets",
"special": null,
"events": [
{
"kind": "rise",
"instant": "2026-07-28T04:01:19.561+01:00",
"azimuth_deg": 57.239438,
"altitude_deg": -0.56607
},
{
"kind": "upper_transit",
"instant": "2026-07-28T11:48:53.172+01:00",
"azimuth_deg": 179.999441,
"altitude_deg": 57.819913
},
{
"kind": "set",
"instant": "2026-07-28T19:37:16.045+01:00",
"azimuth_deg": 302.979875,
"altitude_deg": -0.566067
},
{
"kind": "lower_transit",
"instant": "2026-07-28T23:47:58.181+01:00",
"azimuth_deg": 359.999996,
"altitude_deg": -18.629207
}
],
"rise": [
"2026-07-28T04:01:19.561+01:00"
],
"set": [
"2026-07-28T19:37:16.045+01:00"
],
"transit": [
"2026-07-28T11:48:53.172+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",
"twilight": {
"altitude_at_civil_dusk_deg": -12.897916,
"altitude_at_civil_dawn_deg": 4.368817,
"glare_rule_overridden": false
}
},
{
"name": "Venus",
"code": "venus",
"kind": "planet",
"constellation": {
"name": "Leo",
"abbreviation": "Leo"
},
"tropical_sign": {
"name": "Virgo",
"code": "virgo",
"sign_index": 5,
"degree_in_sign": 20.815146,
"degree_in_sign_whole": 20
},
"position": {
"equatorial": {
"j2000": {
"ra_hours": 11.420632,
"ra_string": "11h 25m 14.3s",
"dec_deg": 4.004635,
"dec_string": "+04 00 16.7"
},
"of_date": {
"ra_hours": 11.443602,
"ra_string": "11h 26m 37.0s",
"dec_deg": 3.857615,
"dec_string": "+03 51 27.4"
}
},
"ecliptic": {
"true_of_date": {
"longitude_deg": 170.815146,
"latitude_deg": 0.239064
}
},
"horizontal": {
"azimuth_deg": 278.196298,
"compass": "W",
"altitude_refracted_deg": -0.930942,
"altitude_unrefracted_deg": -1.573357,
"refraction_deg": 0.642415,
"hour_angle_hours": 6.493692,
"parallactic_angle_deg": 38.159728
},
"distance_au": 0.825021,
"distance_km": 123421371.698546,
"distance_light_minutes": 6.86149,
"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 center 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.227831,
"phase_angle_deg": 81.525221,
"illuminated_fraction": 0.573687,
"illuminated_percent": 57.368702,
"ring_tilt_deg": null
},
"visibility": {
"elongation_deg": 44.99429,
"sun_side": "evening",
"ecliptic_separation_deg": 44.993792,
"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:12.893+01:00",
"instant_precision_seconds": 2,
"days_from_query": 66.402927
}
},
"rise_set": {
"status": "normal",
"status_label": "Rises and sets",
"special": null,
"events": [
{
"kind": "lower_transit",
"instant": "2026-07-28T04:00:31.702+01:00",
"azimuth_deg": 359.999999,
"altitude_deg": -33.884475
},
{
"kind": "rise",
"instant": "2026-07-28T09:36:04.041+01:00",
"azimuth_deg": 82.659734,
"altitude_deg": -0.566072
},
{
"kind": "upper_transit",
"instant": "2026-07-28T16:00:26.242+01:00",
"azimuth_deg": 179.999989,
"altitude_deg": 42.530274
},
{
"kind": "set",
"instant": "2026-07-28T22:23:29.161+01:00",
"azimuth_deg": 276.920893,
"altitude_deg": -0.566067
}
],
"rise": [
"2026-07-28T09:36:04.041+01:00"
],
"set": [
"2026-07-28T22:23:29.161+01:00"
],
"transit": [
"2026-07-28T16:00:26.242+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",
"twilight": {
"altitude_at_civil_dusk_deg": 7.017111,
"altitude_at_civil_dawn_deg": -33.371236,
"glare_rule_overridden": false
}
},
{
"name": "Mars",
"code": "mars",
"kind": "planet",
"constellation": {
"name": "Taurus",
"abbreviation": "Tau"
},
"tropical_sign": {
"name": "Gemini",
"code": "gemini",
"sign_index": 2,
"degree_in_sign": 20.965132,
"degree_in_sign_whole": 20
},
"position": {
"equatorial": {
"j2000": {
"ra_hours": 5.317248,
"ra_string": "05h 19m 02.1s",
"dec_deg": 23.151779,
"dec_string": "+23 09 06.4"
},
"of_date": {
"ra_hours": 5.344281,
"ra_string": "05h 20m 39.4s",
"dec_deg": 23.179878,
"dec_string": "+23 10 47.6"
}
},
"ecliptic": {
"true_of_date": {
"longitude_deg": 80.965132,
"latitude_deg": 0.050998
}
},
"horizontal": {
"azimuth_deg": 8.459692,
"compass": "N",
"altitude_refracted_deg": -14.388245,
"altitude_unrefracted_deg": -14.933599,
"refraction_deg": 0.545354,
"hour_angle_hours": 12.593012,
"parallactic_angle_deg": -5.720229
},
"distance_au": 2.010168,
"distance_km": 300716922.60971,
"distance_light_minutes": 16.718061,
"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 center 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.658976,
"phase_angle_deg": 29.029028,
"illuminated_fraction": 0.937187,
"illuminated_percent": 93.718699,
"ring_tilt_deg": null
},
"visibility": {
"elongation_deg": 44.856245,
"sun_side": "morning",
"ecliptic_separation_deg": 44.856222,
"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:18.437+00:00",
"instant_precision_seconds": 2,
"days_from_query": 165.646741
}
},
"rise_set": {
"status": "normal",
"status_label": "Rises and sets",
"special": null,
"events": [
{
"kind": "rise",
"instant": "2026-07-28T01:40:47.139+01:00",
"azimuth_deg": 49.978139,
"altitude_deg": -0.566069
},
{
"kind": "upper_transit",
"instant": "2026-07-28T09:54:56.480+01:00",
"azimuth_deg": 179.999997,
"altitude_deg": 61.680124
},
{
"kind": "set",
"instant": "2026-07-28T18:09:23.901+01:00",
"azimuth_deg": 310.103001,
"altitude_deg": -0.566067
},
{
"kind": "lower_transit",
"instant": "2026-07-28T21:54:26.601+01:00",
"azimuth_deg": 359.999994,
"altitude_deg": -14.801421
}
],
"rise": [
"2026-07-28T01:40:47.139+01:00"
],
"set": [
"2026-07-28T18:09:23.901+01:00"
],
"transit": [
"2026-07-28T09:54:56.480+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",
"twilight": {
"altitude_at_civil_dusk_deg": -14.686134,
"altitude_at_civil_dawn_deg": 24.216329,
"glare_rule_overridden": false
}
},
{
"name": "Jupiter",
"code": "jupiter",
"kind": "planet",
"constellation": {
"name": "Cancer",
"abbreviation": "Cnc"
},
"tropical_sign": {
"name": "Leo",
"code": "leo",
"sign_index": 4,
"degree_in_sign": 6.273787,
"degree_in_sign_whole": 6
},
"position": {
"equatorial": {
"j2000": {
"ra_hours": 8.559681,
"ra_string": "08h 33m 34.9s",
"dec_deg": 19.251793,
"dec_string": "+19 15 06.5"
},
"of_date": {
"ra_hours": 8.585276,
"ra_string": "08h 35m 07.0s",
"dec_deg": 19.160562,
"dec_string": "+19 09 38.0"
}
},
"ecliptic": {
"true_of_date": {
"longitude_deg": 126.273787,
"latitude_deg": 0.472265
}
},
"horizontal": {
"azimuth_deg": 322.008952,
"compass": "NW",
"altitude_refracted_deg": -10.715782,
"altitude_unrefracted_deg": -11.287623,
"refraction_deg": 0.571842,
"hour_angle_hours": 9.352018,
"parallactic_angle_deg": 23.944491
},
"distance_au": 6.301023,
"distance_km": 942619698.128479,
"distance_light_minutes": 52.404014,
"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 center 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.12595,
"illuminated_fraction": 0.999999,
"illuminated_percent": 99.999879,
"ring_tilt_deg": null
},
"visibility": {
"elongation_deg": 0.653973,
"sun_side": "evening",
"ecliptic_separation_deg": 0.452432,
"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:59:00.132+00:00",
"instant_precision_seconds": 2,
"days_from_query": 137.14514
}
},
"rise_set": {
"status": "normal",
"status_label": "Rises and sets",
"special": null,
"events": [
{
"kind": "lower_transit",
"instant": "2026-07-28T01:11:34.315+01:00",
"azimuth_deg": 359.99999,
"altitude_deg": -18.800442
},
{
"kind": "rise",
"instant": "2026-07-28T05:22:59.102+01:00",
"azimuth_deg": 57.286438,
"altitude_deg": -0.566125
},
{
"kind": "upper_transit",
"instant": "2026-07-28T13:10:03.514+01:00",
"azimuth_deg": 179.999862,
"altitude_deg": 57.71449
},
{
"kind": "set",
"instant": "2026-07-28T20:56:54.693+01:00",
"azimuth_deg": 302.650698,
"altitude_deg": -0.566071
}
],
"rise": [
"2026-07-28T05:22:59.102+01:00"
],
"set": [
"2026-07-28T20:56:54.693+01:00"
],
"transit": [
"2026-07-28T13:10:03.514+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",
"twilight": {
"altitude_at_civil_dusk_deg": -4.799075,
"altitude_at_civil_dawn_deg": -5.74039,
"glare_rule_overridden": false
}
},
"... 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.
Every retrograde and direct station of every planet in a window, with the loop length, its arc, and the interval of triple crossing.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
body | no | a 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. |
bodies | no | comma-separated planet names, up to 20 | mercury,venus,mars,jupiter,saturn,uranus,neptune,pluto | Which planets to scan for stations. |
motion | no | retrograde | direct | all | all | Filters the returned stations to retrograde (direct-to-retrograde) turns, direct turns, or both. |
include | no | comma-separated: periods, stations, triple_crossing, companion_event, rate_curve | periods, stations | periods 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). |
step | no | duration, 1h to 30d | 1d | Sample stride for include=rate_curve only; it has no effect on the station search itself, which uses a fixed internal stride per body. |
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
time_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | no | opaque 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:27.295Z",
"instant_precision_seconds": 2,
"body": "Mercury",
"value_deg": 230.979608,
"value_unit": "deg",
"days_from_query": 53.302399,
"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:18.281Z",
"instant_precision_seconds": 2,
"body": "Mercury",
"value_deg": 215.033898,
"value_unit": "deg",
"days_from_query": 73.661323,
"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:52.788Z",
"constellations_crossed": [
"Libra",
"Virgo"
],
"triple_crossing_window": {
"start": "2026-10-04T09:10:58.411Z",
"end": "2026-11-30T06:17:03.278Z",
"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:43.227Z",
"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 (pairs=sun-mercury, with max_separation_deg at or above the pair's least separation) lists the apparent geocentric instant of this conjunction in longitude_conjunction_instants, 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.596334,
"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:27.295Z",
"instant_precision_seconds": 2,
"body": "Mercury",
"value_deg": 230.979608,
"value_unit": "deg",
"days_from_query": 53.302399,
"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:18.281Z",
"instant_precision_seconds": 2,
"body": "Mercury",
"value_deg": 215.033898,
"value_unit": "deg",
"days_from_query": 73.661323,
"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"
}
Planet events as dated instants: the Mercury and Venus apparition cycle with its current state, the conjunctions with the Sun, quadratures and oppositions of Mars to Neptune, and IAU constellation entries for all seven.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
body | no | mercury | venus | mars | jupiter | saturn | uranus | neptune | (see bodies) | A single planet. Mutually exclusive with bodies. The Sun, the Moon, Pluto and fixed targets are refused. |
bodies | no | comma-separated: mercury, venus, mars, jupiter, saturn, uranus, neptune | venus,mercury | Which planets to walk. Mercury and Venus have the apparition kinds; Mars to Neptune have the Sun-relative kinds; every one of them can have constellation_entry. |
kinds | no | comma-separated event kinds | each body's own family: the seven apparition kinds for Mercury and Venus, the four Sun-relative kinds for Mars to Neptune | Filters to any of inferior_conjunction, superior_conjunction, greatest_elongation_east, greatest_elongation_west, peak_magnitude, transit_of_mercury, transit_of_venus, conjunction_with_sun, quadrature_west, opposition, quadrature_east, constellation_entry. The apparition kinds (inferior_conjunction to transit_of_venus) apply to Mercury and Venus; peak_magnitude only ever fires for Venus, because Mercury's brightness peaks at superior conjunction, where it cannot be observed. conjunction_with_sun, quadrature_west, opposition and quadrature_east apply to Mars to Neptune: the instants the planet's apparent geocentric ecliptic longitude minus the Sun's reaches 0, 270, 180 and 90 deg, each with the planet's ecliptic_longitude_deg, constellation, distance_from_earth_au, magnitude and, as value_deg, its elongation. Their conventions note names the heliocentric instant /v2/retrogrades gives on its companion_event. constellation_entry applies to every planet and is returned only when listed: each crossing of an IAU constellation boundary, with from_constellation, to_constellation, direction (direct or retrograde), ecliptic_longitude_deg, and pass_index and pass_count, which number a run of consecutive crossings of the boundary between the same two constellations, ended at each conjunction with the Sun that separates two retrograde loops (every conjunction for Mars to Neptune, the superior conjunction for Mercury and Venus), so a run never spans two loops: 3 for a retrograde loop that carries the planet over, back and over again, 2 for a loop that dips back and returns or a corner clipped on the way past, and more when the path also clips a corner of that boundary between the same two conjunctions (at most 7 over 1700-2200). A kind none of the requested bodies can have is ignored with a parameter_ignored warning. |
lat | no | decimal degrees, -90 to 90 | (none) | Optional. With lon, every event gains visible_from_location, altitude_deg and azimuth_deg for that observer at the event instant. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
time_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | no | opaque 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/planet-events?bodies=venus,mercury&start=2026-01-01&end=2027-01-01&lat=51.4778&lon=-0.0015&tz=Europe/London
Example response (real, captured live)
{
"endpoint": "/v2/planet-events",
"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": 51.4778,
"longitude": -0.0015,
"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",
"bodies": [
"venus",
"mercury"
],
"kinds": [
"inferior_conjunction",
"superior_conjunction",
"greatest_elongation_east",
"greatest_elongation_west",
"... 3 more rows omitted for length ..."
],
"ignored": []
},
"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
},
"definition": "An apparition is one synodic cycle of an inferior planet read as a sequence of instants: inferior conjunction begins the morning apparition, superior conjunction begins the evening one, and the greatest elongations and brightness peaks fall between. These are apparent geometry; no meaning is attached to any of them.",
"kinds_selected": [
"inferior_conjunction",
"superior_conjunction",
"greatest_elongation_east",
"greatest_elongation_west",
"... 3 more rows omitted for length ..."
],
"body_count": 2,
"event_count": 17,
"bodies": [
{
"name": "Venus",
"code": "venus",
"synodic_period_days_mean": 583.92,
"synodic_note": "The synodic period is a mean. Individual intervals vary by weeks with orbital eccentricity; use the event instants, not the mean.",
"events": [
{
"kind": "superior_conjunction",
"label": "Venus at superior conjunction, 0.7 deg from the Sun",
"instant": "2026-01-06T15:53:36.994+00:00",
"instant_precision_seconds": 60,
"body": "Venus",
"value_deg": 0.709994,
"value_unit": "deg",
"days_from_query": 5.7,
"visible_from_location": true,
"altitude_deg": 0.765202,
"azimuth_deg": 230.335426,
"href": "https://www.cyclecalcs.com/planets/venus.html",
"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 as seen from the Sun. Apparent geocentric instants for the same event differ by minutes up to more than an hour."
},
{
"kind": "greatest_elongation_east",
"label": "Venus at greatest eastern elongation, 45.9 deg",
"instant": "2026-08-15T07:23:09.478+01:00",
"instant_precision_seconds": 3600,
"body": "Venus",
"value_deg": 45.891471,
"value_unit": "deg",
"days_from_query": 226.3,
"visible_from_location": false,
"altitude_deg": -25.759416,
"azimuth_deg": 61.631075,
"href": "https://www.cyclecalcs.com/planets/venus.html"
},
{
"kind": "peak_magnitude",
"label": "Venus at peak brightness, magnitude -4.8",
"instant": "2026-09-22T14:33:48.480+01:00",
"instant_precision_seconds": 3600,
"body": "Venus",
"value_mag": -4.77529,
"value_unit": "mag",
"days_from_query": 264.6,
"visible_from_location": true,
"altitude_deg": 18.832244,
"azimuth_deg": 172.974796,
"href": "https://www.cyclecalcs.com/planets/venus.html",
"definition_note": "The instant the Astronomy Engine magnitude model reaches its maximum. Published \"greatest brilliancy\" dates using other definitions, such as greatest illuminated extent, can differ by several days."
},
{
"kind": "inferior_conjunction",
"label": "Venus at inferior conjunction, 6.5 deg from the Sun",
"instant": "2026-10-24T04:33:29.652+01:00",
"instant_precision_seconds": 60,
"body": "Venus",
"value_deg": 6.516795,
"value_unit": "deg",
"days_from_query": 296.1,
"visible_from_location": false,
"altitude_deg": -32.190488,
"azimuth_deg": 77.30226,
"href": "https://www.cyclecalcs.com/planets/venus.html",
"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 as seen from the Sun. Apparent geocentric instants for the same event differ by minutes up to more than an hour."
},
"... 1 more row omitted for length ..."
]
},
{
"name": "Mercury",
"code": "mercury",
"synodic_period_days_mean": 115.88,
"synodic_note": "The synodic period is a mean. Individual intervals vary by weeks with orbital eccentricity; use the event instants, not the mean.",
"events": [
{
"kind": "superior_conjunction",
"label": "Mercury at superior conjunction, 2.1 deg from the Sun",
"instant": "2026-01-21T15:32:34.417+00:00",
"instant_precision_seconds": 60,
"body": "Mercury",
"value_deg": 2.053099,
"value_unit": "deg",
"days_from_query": 20.6,
"visible_from_location": true,
"altitude_deg": 4.893778,
"azimuth_deg": 225.386599,
"href": "https://www.cyclecalcs.com/planets/mercury.html",
"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 as seen from the Sun. Apparent geocentric instants for the same event differ by minutes up to more than an hour."
},
{
"kind": "greatest_elongation_east",
"label": "Mercury at greatest eastern elongation, 18.1 deg",
"instant": "2026-02-19T17:41:42.598+00:00",
"instant_precision_seconds": 3600,
"body": "Mercury",
"value_deg": 18.123216,
"value_unit": "deg",
"days_from_query": 49.7,
"visible_from_location": true,
"altitude_deg": 12.424736,
"azimuth_deg": 248.480087,
"href": "https://www.cyclecalcs.com/planets/mercury.html"
},
{
"kind": "inferior_conjunction",
"label": "Mercury at inferior conjunction, 3.6 deg from the Sun",
"instant": "2026-03-07T10:55:48.924+00:00",
"instant_precision_seconds": 60,
"body": "Mercury",
"value_deg": 3.635781,
"value_unit": "deg",
"days_from_query": 65.5,
"visible_from_location": true,
"altitude_deg": 34.708516,
"azimuth_deg": 158.699945,
"href": "https://www.cyclecalcs.com/planets/mercury.html",
"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 as seen from the Sun. Apparent geocentric instants for the same event differ by minutes up to more than an hour."
},
{
"kind": "greatest_elongation_west",
"label": "Mercury at greatest western elongation, 27.8 deg",
"instant": "2026-04-03T23:34:37.540+01:00",
"instant_precision_seconds": 3600,
"body": "Mercury",
"value_deg": 27.819776,
"value_unit": "deg",
"days_from_query": 92.9,
"visible_from_location": false,
"altitude_deg": -45.09386,
"azimuth_deg": 3.904964,
"href": "https://www.cyclecalcs.com/planets/mercury.html"
},
"... 8 more rows omitted for length ..."
]
}
]
},
"warnings": [],
"links": {
"self": "https://www.cyclecalcs.com/v2/planet-events?bodies=venus%2Cmercury&end=2027-01-01&lat=51.4778&lon=-0.0015&start=2026-01-01&tz=Europe%2FLondon",
"docs": "https://www.cyclecalcs.com/api/reference.html#planet-events",
"spec": "https://www.cyclecalcs.com/v2/openapi.json",
"page": "https://www.cyclecalcs.com/venus-morning-star.html",
"stations": "https://www.cyclecalcs.com/v2/retrogrades",
"related": [
{
"rel": "body",
"title": "Venus",
"href": "https://www.cyclecalcs.com/planets/venus.html"
},
{
"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#planet-events"
}
Some arrays are shortened above for length; the live response returns every row.
Close approaches between body pairs in a window, ranked, with the separation and whether the pair is observable or lost in twilight.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
bodies | no | comma-separated body names and star:<name> or hr:<n> named stars, up to 8 | sun,moon,mercury,venus,mars,jupiter,saturn | The pool of bodies to search for close approaches; every pair among them is checked, except a pair of two named stars, which never approach each other. Mutually exclusive with pairs. A radec: target is refused here; /v2/separation takes one. |
pairs | no | comma-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,moon-star:regulus. A named star may be one half of a pair, never both. |
max_separation_deg | no | degrees, 0 to 180 | 5 | Only 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_deg | no | degrees, 0 to 180 | 0 | Excludes a close approach whose solar elongation is under this figure, that is, one that happens too near the Sun's glare to observe. |
kind | no | conjunction | close_approach | all | all | conjunction: the two apparent ecliptic longitudes are equal at some instant in the approach, anywhere between the separation maxima either side of the minimum; longitude_conjunction_instants lists every such crossing, two or three when retrograde loops bring the pair past each other more than once. close_approach: the approach holds no such instant. all returns both. |
frame | no | apparent | geocentric | ecliptic | apparent | The frame the separation is measured in. The conjunction test always compares apparent geocentric ecliptic longitudes. With ecliptic, separation_deg is the longitude difference alone and sky_separation_deg gives the true angle on the sky, geocentric like that longitude difference even when lat and lon are given. ecliptic works for a named star as for a body. |
step | no | duration | (an internal search stride) | Sample stride for the minimum-finding search. |
sort | no | time | separation | time | Order of the returned rows: chronological, or closest approach first. |
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
time_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | no | opaque 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:56.997Z",
"instant_precision_seconds": 1,
"body": null,
"value_deg": 0.173518,
"value_unit": "deg",
"days_from_query": 7.133299,
"pair": {
"a": "Venus",
"b": "Mars"
},
"separation_deg": 0.173518,
"separation_arcmin": 10.411086,
"position_angle_of_b_from_a_deg": 175.575903,
"ecliptic_longitude_difference_deg": -0.010057,
"ecliptic_latitude_difference_deg": -0.173226,
"longitude_conjunction_instant": "2026-01-08T02:42:12.616Z",
"minimum_offset_from_longitude_conjunction_minutes": 29.739691,
"elongation_from_sun_deg": 0.835116,
"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",
"sky_separation_deg": 0.173518,
"longitude_conjunction_instants": [
"2026-01-08T02:42:12.616Z"
],
"magnitudes_extrapolated": {
"a": null,
"b": null
}
},
{
"kind": "conjunction",
"label": "Venus and Jupiter in conjunction, 1.61 deg apart",
"instant": "2026-06-09T19:47:38.426Z",
"instant_precision_seconds": 1,
"body": null,
"value_deg": 1.608214,
"value_unit": "deg",
"days_from_query": 159.82475,
"pair": {
"a": "Venus",
"b": "Jupiter"
},
"separation_deg": 1.608214,
"separation_arcmin": 96.492833,
"position_angle_of_b_from_a_deg": 190.562089,
"ecliptic_longitude_difference_deg": 0.007405,
"ecliptic_latitude_difference_deg": -1.608197,
"longitude_conjunction_instant": "2026-06-09T19:58:36.938Z",
"minimum_offset_from_longitude_conjunction_minutes": -10.975205,
"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",
"sky_separation_deg": 1.608214,
"longitude_conjunction_instants": [
"2026-06-09T19:58:36.938Z"
],
"magnitudes_extrapolated": {
"a": null,
"b": null
}
},
{
"kind": "conjunction",
"label": "Jupiter and Mars in conjunction, 1.19 deg apart",
"instant": "2026-11-16T02:03:11.596Z",
"instant_precision_seconds": 1,
"body": null,
"value_deg": 1.193722,
"value_unit": "deg",
"days_from_query": 319.085551,
"pair": {
"a": "Jupiter",
"b": "Mars"
},
"separation_deg": 1.193722,
"separation_arcmin": 71.623343,
"position_angle_of_b_from_a_deg": 16.666025,
"ecliptic_longitude_difference_deg": -0.065311,
"ecliptic_latitude_difference_deg": 1.191935,
"longitude_conjunction_instant": "2026-11-16T06:22:05.105Z",
"minimum_offset_from_longitude_conjunction_minutes": -258.891816,
"elongation_from_sun_deg": 87.850394,
"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",
"sky_separation_deg": 1.193722,
"longitude_conjunction_instants": [
"2026-11-16T06:22:05.105Z"
],
"magnitudes_extrapolated": {
"a": null,
"b": null
}
}
]
},
"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"
}
The angular separation between any two targets at an instant, and the minima of that separation across a window.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
a | yes | a body name, star:<name> or hr:<n> (one of 80 named stars, listed at /v2/enums?set=stars), or radec:<ra>,<dec>[,j2000] | (required) | The first target. Required; must differ from b. |
b | yes | a body name, star:<name> or hr:<n> (one of 80 named stars, listed at /v2/enums?set=stars), or radec:<ra>,<dec>[,j2000] | (required) | The second target. Required; must differ from a. For a named star or radec target, the distance_au in its a_position or b_position is a placeholder the engine needs, not a distance to use, and the row says so in distance_is_placeholder.a or .b and distance_note.a or .b. A range search over two fixed targets finds no approach: with a named star it is refused as BAD_PAIR, and a radec pair is warned fixed_pair_minima. |
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
step | no | duration: 1min, 15min, 6h, 1d, 7d | endpoint-specific | The stride of a range query, narrower on this endpoint than the generic 1 minute to 1 year. Only meaningful together with start. |
max_separation_deg | no | degrees, 0 to 180 | 10 | In a range query, only local minima at or under this separation are returned. Filters the result and never enters the search itself. |
frame | no | apparent | geocentric | ecliptic | apparent | The frame the separation is measured in. With ecliptic, separation_deg is the difference in apparent geocentric ecliptic longitude alone, for a named star or radec target as for a body, and sky_separation_deg gives the true angle on the sky, geocentric like that longitude difference even when lat and lon are given. |
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
time_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv, txt. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | no | opaque 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:38.818Z",
"instant_precision_seconds": 1,
"separation_deg": 0.098793,
"separation_arcmin": 5.92757,
"separation_arcsec": 355.654222,
"days_from_query": 355.768505,
"longitude_conjunction_instant": "2020-12-21T18:26:17.696Z",
"ecliptic_longitude_difference_deg": -0.000027,
"ecliptic_latitude_difference_deg": 0.098793,
"elongation_from_sun_deg": 30.133715,
"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.542231,
"sky_separation_deg": 0.098793,
"longitude_conjunction_instants": [
"2020-12-21T18:26:17.696Z"
],
"distance_is_placeholder": {
"a": false,
"b": false
}
}
],
"closest": {
"kind": "conjunction",
"instant": "2020-12-21T18:26:38.818Z",
"instant_precision_seconds": 1,
"separation_deg": 0.098793,
"separation_arcmin": 5.92757,
"separation_arcsec": 355.654222,
"days_from_query": 355.768505,
"longitude_conjunction_instant": "2020-12-21T18:26:17.696Z",
"ecliptic_longitude_difference_deg": -0.000027,
"ecliptic_latitude_difference_deg": 0.098793,
"elongation_from_sun_deg": 30.133715,
"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.542231,
"sky_separation_deg": 0.098793,
"longitude_conjunction_instants": [
"2020-12-21T18:26:17.696Z"
],
"distance_is_placeholder": {
"a": false,
"b": false
}
},
"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"
}
How rare a pairing is: for two targets at an instant, the most recent earlier pairing that was at least as close and the next one, searched up to ten years each way; with a location, by default only pairings seen in a dark sky with both targets up count.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
a | yes | a body name, star:<name> or hr:<n> (one of 80 named stars, listed at /v2/enums?set=stars), or radec:<ra>,<dec>[,j2000] | (required) | The first target. Required; must differ from b, and at most one of the two may be a fixed target, a named star or a radec: point. |
b | yes | a body name, star:<name> or hr:<n> (one of 80 named stars, listed at /v2/enums?set=stars), or radec:<ra>,<dec>[,j2000] | (required) | The second target. Required; must differ from a. |
at | no | ISO 8601 date or datetime | now | The instant the pairing was seen. The walk runs outward from it in both directions. This endpoint has no range mode. |
frame | no | apparent | geocentric | apparent | apparent is the true angle between the two apparent directions, topocentric when lat and lon are given, the frame a photograph holds. geocentric ignores the location. No ecliptic-longitude convention is offered here. |
span_days | no | days, 1 to 3660 | 3660 | How far the walk runs EACH WAY from at, so the window searched is twice this. The default is the maximum, because "closest in more than ten years" is only true when the whole span was walked. A side that reaches the span, or the 1700 to 2200 edge of the ephemeris range, without a match is null, flagged truncated and warned; the sentence then names what was actually walked. |
exclude_days | no | days, 0 to 30 | 1.5 | visible=any only. A separation minimum closer to at than this belongs to the pass being observed and is never reported as previous or next, so the answer cannot say the last time the Moon was this close was eight hours ago. Under visible=dark the stretch the photograph was taken in plays this part, and exclude_days is echoed but ignored, with a warning. |
visible | no | dark | any | dark with a location, frame=apparent and no Sun; any otherwise | dark counts a pairing only while both targets are at least 3 degrees above the horizon, refracted, and the Sun at least 6 degrees below it: one unbroken stretch of that is a sighting, valued at its smallest separation, and previous and next are the nearest sightings at least as close. The stretch at is in, with both targets above the horizon in daylight or dark and holding one dark night (its own, or for a photo in daylight or twilight the nearer one, at most 18 hours either way), is never previous or next; a true minimum inside it, in a dark sky, closer than at is returned as closer.before or closer.after, and the sentence says so, for example "Closer still at 04:54". Rows gain the three altitudes and at_window_edge; dates within six months print their day. any counts every minimum of the separation, above the horizon or not, the answer this endpoint gave before the dark-sky rule. dark needs a location and frame=apparent and refuses the Sun. |
lat | no | decimal degrees, -90 to 90 | (none) | Optional. With lon, every separation in the walk is topocentric, which for the Moon moves a minimum by up to about a degree and its instant by hours, and, with frame=apparent and no Sun in the pair, the default rule becomes visible=dark: only pairings seen from here in a dark sky count. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
time_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv, txt. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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/rarity?a=moon&b=jupiter&at=2026-09-08T09:56:30Z&lat=38.9696&lon=-77.3861&elevation_m=100&tz=America/New_York
Example response (real, captured live)
{
"endpoint": "/v2/rarity",
"computed_at": "2026-08-01T00:00:00.000Z",
"query": {
"at": "2026-09-08T09:56:30.000Z",
"mode": "instant",
"resolved_from": "at",
"is_now": false,
"latitude": 38.9696,
"longitude": -77.3861,
"elevation_m": 100,
"tz": "America/New_York",
"utc_offset_seconds": -14400,
"utc_offset_iso": "-04:00",
"is_dst": true,
"timezone_source": "parameter",
"time_format": "iso",
"format": "json",
"shape": "nested",
"precision": 6,
"pretty": 1,
"verbosity": "full",
"a": "Moon",
"b": "Jupiter",
"frame": "apparent",
"span_days": 3660,
"exclude_days": 1.5,
"visible": "dark",
"ignored": []
},
"data": {
"pair": {
"a": "Moon",
"b": "Jupiter"
},
"instant": "2026-09-08T05:56:30.000-04:00",
"frame": "apparent",
"origin": "topocentric",
"visible": "dark",
"separation_deg": 4.305766,
"separation_arcmin": 258.345935,
"window": {
"start": "2026-09-08T04:11:37.582-04:00",
"end": "2026-09-08T06:17:42.888-04:00"
},
"closer": {
"before": null,
"after": null
},
"previous": {
"instant": "2026-04-22T20:54:23.912-04:00",
"separation_deg": 3.175992,
"separation_arcmin": 190.55953,
"days_before": 138.376459,
"altitude_a_deg": 56.586904,
"altitude_b_deg": 53.944948,
"sun_altitude_deg": -11.786588,
"at_window_edge": false,
"instant_precision_seconds": 1
},
"next": {
"instant": "2026-10-06T04:51:33.539-04:00",
"separation_deg": 0.034524,
"separation_arcmin": 2.071417,
"days_after": 27.954902,
"altitude_a_deg": 22.960564,
"altitude_b_deg": 22.96956,
"sun_altitude_deg": -27.502597,
"at_window_edge": false,
"instant_precision_seconds": 1
},
"truncated": {
"previous": false,
"next": false
},
"searched": {
"start": "2016-08-31T05:56:30.000-04:00",
"end": "2036-09-15T05:56:30.000-04:00",
"span_days": 3660,
"stride_days": 1,
"exclude_days": 1.5,
"visible": "dark",
"min_altitude_deg": 3,
"window_min_altitude_deg": 0,
"sun_max_altitude_deg": -6,
"resolution_seconds": 60
},
"sentence": "Closest Moon-Jupiter pairing since 22 Apr. Next: 6 Oct.",
"definition": "Separation is the true angle between the two apparent directions, centre to centre, airless."
},
"warnings": [],
"links": {
"self": "https://www.cyclecalcs.com/v2/rarity?a=moon&at=2026-09-08T09%3A56%3A30Z&b=jupiter&elevation_m=100&lat=38.9696&lon=-77.3861&tz=America%2FNew_York",
"docs": "https://www.cyclecalcs.com/api/reference.html#rarity",
"spec": "https://www.cyclecalcs.com/v2/openapi.json",
"page": "https://www.cyclecalcs.com/conjunctions/",
"related": [
{
"rel": "endpoint",
"title": "The separation at this instant, with its rate of change",
"href": "https://www.cyclecalcs.com/v2/separation?a=moon&b=jupiter&at=2026-09-08T09:56:30.000Z"
},
{
"rel": "endpoint",
"title": "Close approaches across many pairs",
"href": "https://www.cyclecalcs.com/v2/conjunctions"
}
]
},
"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#rarity"
}
Observing tools
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.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
count | no | integer | endpoint-specific | How many rows to return from start. Mutually exclusive with end. |
step | no | duration: 1d | endpoint-specific | The stride of a range query, narrower on this endpoint than the generic 1 minute to 1 year. It answers a per-day question and takes no other stride. Only meaningful together with start. |
lat | yes | decimal degrees, -90 to 90 | (required) | Required; every twilight boundary is defined relative to a horizon. |
lon | yes | decimal degrees, -180 to 180 | (required) | Required alongside lat. |
elevation_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
day_anchor | no | civil | solar | utc | civil if tz or place set a zone, else solar | What 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. |
depressions | no | comma-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. |
refraction | no | normal | none | normal | normal 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_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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. |
limit | no | integer | endpoint-specific page size | Page size for a paginated list response. Use together with cursor for later pages. |
cursor | no | opaque 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,
"local_date": "2026-11-14",
"weekday": {
"iso_weekday": 6,
"name": "Saturday"
}
},
"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.899+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.899+00:00",
"end": "2026-11-14T05:59:15.706+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.706+00:00",
"end": "2026-11-14T06:39:54.267+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.267+00:00",
"end": "2026-11-14T07:16:44.756+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.864+00:00",
"transit_altitude_deg": 20.237711,
"antitransit": "2026-11-14T23:45:00.671+00:00",
"antitransit_altitude_deg": -56.677064
},
"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 center 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.
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.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The first night to evaluate; nights counts forward from it. This endpoint has no range mode. |
lat | yes | decimal degrees, -90 to 90 | (required) | Required; the dark window is defined relative to a horizon and the local night. |
lon | yes | decimal degrees, -180 to 180 | (required) | Required alongside lat. |
elevation_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
nights | no | integer, 1 to 62 | 1 | How many consecutive nights to rank, starting from at. |
sun_depression_deg | no | degrees, 6 to 30 | 18 (astronomical twilight) | How far below the horizon the Sun must be for the sky to count as dark. |
moon_altitude_max_deg | no | degrees, -90 to 90 | 0 (Moon below the horizon) | The Moon must be at or below this altitude for a minute to count as moonless. |
moon_illumination_max | no | fraction, 0 to 1 | 1 (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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
time_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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 center 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",
"night_of_weekday": {
"iso_weekday": 6,
"name": "Saturday"
},
"label": "Night of Saturday 14 November 2026",
"dark_interval": {
"kind": "astronomical_night",
"label": "Astronomical night",
"start": "2026-11-14T18:09:13.046+00:00",
"end": "2026-11-15T05:21:23.639+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:09.774+00:00",
"end": "2026-11-15T05:21:23.639+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.271386,
"phase_name": "waxing_crescent",
"rise": "2026-11-14T12:16:26.371+00:00",
"set": "2026-11-14T19:50:09.774+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",
"night_of_weekday": {
"iso_weekday": 7,
"name": "Sunday"
},
"label": "Night of Sunday 15 November 2026",
"dark_interval": {
"kind": "astronomical_night",
"label": "Astronomical night",
"start": "2026-11-15T18:08:07.911+00:00",
"end": "2026-11-16T05:22:48.633+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.423+00:00",
"end": "2026-11-16T05:22:48.633+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.358943,
"phase_name": "waxing_crescent",
"rise": "2026-11-15T12:40:39.377+00:00",
"set": "2026-11-15T21:02:13.423+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",
"night_of_weekday": {
"iso_weekday": 1,
"name": "Monday"
},
"label": "Night of Monday 16 November 2026",
"dark_interval": {
"kind": "astronomical_night",
"label": "Astronomical night",
"start": "2026-11-16T18:07:05.178+00:00",
"end": "2026-11-17T05:24:12.847+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:30.821+00:00",
"end": "2026-11-17T05:24:12.847+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.452725,
"phase_name": "first_quarter",
"rise": "2026-11-16T12:59:01.427+00:00",
"set": "2026-11-16T22:15:30.821+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"
}
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.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
step | no | duration: 1min, 1h, 6h, 1d, 7d, 1mo, 1y | endpoint-specific | The stride of a range query, from 1 minute to 1 year. Only meaningful together with start. |
count | no | integer | endpoint-specific | How many rows to return from start. Mutually exclusive with end. |
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
time_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv, txt. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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.722488,
"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.416822,
"obliquity_arcsec": 7.308325,
"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. UT1 is assumed equal to UTC, so 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"
}
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.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
step | no | duration: 1min, 1h, 6h, 1d, 7d, 1mo, 1y | endpoint-specific | The stride of a range query, from 1 minute to 1 year. Only meaningful together with start. |
count | no | integer | endpoint-specific | How many rows to return from start. Mutually exclusive with end. |
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
include | no | comma-separated: basic, extremes | basic | basic 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_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv, txt. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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.513833,
"equation_of_time_seconds": 930.830003,
"equation_of_time_hms": "+00h 15m 30.8s",
"sun": {
"declination_deg": -18.400363,
"right_ascension_hours": 15.341181,
"ecliptic_longitude_deg": 232.525378,
"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.864Z",
"mean_noon_local": "2026-11-14T12:00:30.240Z",
"apparent_noon_local": "2026-11-14T11:44:55.864Z",
"sundial_correction_minutes": -15.572933,
"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"
}
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.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
step | no | duration: 1min, 1h, 6h, 1d, 7d, 1mo, 1y | endpoint-specific | The stride of a range query, from 1 minute to 1 year. Only meaningful together with start. |
count | no | integer | endpoint-specific | How many rows to return from start. Mutually exclusive with end. |
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
include | no | comma-separated: basic, terminator | basic | basic 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. |
refraction | no | normal | none | normal | normal 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_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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.476755,
"latitude_deg": 3.448855,
"total_deg": 4.246047,
"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 disk from sky east."
},
"sub_earth_point": {
"selenographic_longitude_deg": -2.476755,
"selenographic_latitude_deg": 3.448855,
"definition": "the point on the Moon's surface at the center of the disk as seen from the center of the Earth. Its selenographic coordinates are the libration angles."
},
"sub_solar_point": {
"selenographic_longitude_deg": 115.69999,
"selenographic_latitude_deg": -1.55599,
"selenographic_colongitude_deg": 334.30001,
"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.235153,
"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.308239,
"bright_limb_definition": "the direction of the midpoint of the bright limb, measured from celestial north through east."
},
"geometry": {
"distance_km": 404878.91379,
"angular_diameter_deg": 0.491732,
"angular_diameter_arcmin": 29.50393,
"ecliptic_longitude_deg": 294.138202,
"ecliptic_latitude_deg": -2.599945,
"illuminated_fraction": 0.263566,
"phase_angle_deg": 61.615099,
"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"
}
Where the four Galilean moons are, and every transit, shadow transit, eclipse and occultation in the window, with both contact instants for each.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
at | no | ISO 8601 date or datetime | now | The instant to evaluate. A date-only value (2026-07-27) is midnight in the effective time zone, or the first instant after a clock change that skips midnight; the basic format without separators (20260727T210000Z) reads the same as the extended one; 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. |
start | no | ISO 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. |
end | no | ISO 8601 date or datetime | (none) | Closes a range opened by start; must be strictly after it. Alias: to. Mutually exclusive with count. |
step | no | duration, 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. |
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
moons | no | comma-separated: Io, Europa, Ganymede, Callisto | all four | Restrict the scan to specific moons. |
events | no | comma-separated: transit, shadow, eclipse, occultation | all four | Restrict the scan to specific event families: the moon crossing Jupiter's disk, its shadow crossing the disk, the moon entering Jupiter's shadow, or Jupiter occulting the moon. |
refraction | no | normal | none | normal | normal 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_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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.277412,
"angular_equatorial_radius_arcsec": 18.67825,
"angular_polar_radius_arcsec": 17.46651,
"equatorial_radius_km": 71492,
"polar_radius_km": 66854,
"axis_position_angle_deg": 21.871393,
"magnitude": -2.1,
"phase_angle_deg": 10.700271
},
"moons": [
{
"name": "io",
"label": "Io",
"radius_km": 1821.6,
"offset_east_rj": 0.882188,
"offset_north_rj": -0.328395,
"offset_line_of_sight_rj": -5.802817,
"offset_east_arcsec": 16.47772,
"offset_north_arcsec": -6.133847,
"in_front": true,
"state": "transiting",
"shadow_offset_east_rj": 0.027898,
"shadow_offset_north_rj": -0.021825,
"shadow_frame": "the same sky frame as the moon offsets, so both can be drawn on one disk",
"shadow_on_disc": true
},
{
"name": "europa",
"label": "Europa",
"radius_km": 1560.8,
"offset_east_rj": 1.016529,
"offset_north_rj": -0.527989,
"offset_line_of_sight_rj": 9.231718,
"offset_east_arcsec": 18.986982,
"offset_north_arcsec": -9.861914,
"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 disk",
"shadow_on_disc": false
},
{
"name": "ganymede",
"label": "Ganymede",
"radius_km": 2631.2,
"offset_east_rj": 3.713948,
"offset_north_rj": -1.547744,
"offset_line_of_sight_rj": 14.461881,
"offset_east_arcsec": 69.370052,
"offset_north_arcsec": -28.909158,
"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 disk",
"shadow_on_disc": false
},
{
"name": "callisto",
"label": "Callisto",
"radius_km": 2410.3,
"offset_east_rj": -11.612064,
"offset_north_rj": 4.449667,
"offset_line_of_sight_rj": 23.277032,
"offset_east_arcsec": -216.893035,
"offset_north_arcsec": 83.111986,
"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 disk",
"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:18:03.000Z",
"instant_precision_seconds": 1,
"body": "Ganymede",
"value_deg": null,
"value_unit": null,
"days_from_query": -0.390243,
"contact_interior_instant": "2026-11-15T00:26:11.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 disk.",
"href": "https://www.cyclecalcs.com/planets/jupiter.html"
},
{
"kind": "moon_eclipse_ingress",
"label": "Europa enters Jupiter shadow",
"instant": "2026-11-15T04:04:05.000Z",
"instant_precision_seconds": 1,
"body": "Europa",
"value_deg": null,
"value_unit": null,
"days_from_query": -0.233275,
"contact_interior_instant": "2026-11-15T04:07:52.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 disk.",
"href": "https://www.cyclecalcs.com/planets/jupiter.html"
},
{
"kind": "moon_occultation_egress",
"label": "Ganymede emerges from behind Jupiter",
"instant": "2026-11-15T04:06:01.000Z",
"instant_precision_seconds": 1,
"body": "Ganymede",
"value_deg": null,
"value_unit": null,
"days_from_query": -0.231933,
"contact_interior_instant": "2026-11-15T03:57:53.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:12.000Z",
"instant_precision_seconds": 1,
"body": "Europa",
"value_deg": null,
"value_unit": null,
"days_from_query": -0.129028,
"contact_interior_instant": "2026-11-15T06:38:01.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 disk.",
"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 kilometer.",
"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.
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.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
bortle | no | integer, 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. |
nelm | no | magnitude, 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. |
equipment | no | naked_eye | binoculars | telescope | naked_eye | What you are observing with. telescope requires aperture_mm. |
aperture_mm | no | millimetres, 5 to 5000 | (required when equipment=telescope) | The aperture of the telescope or binoculars. Ignored, with a warning, when equipment=naked_eye. |
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
at | no | ISO 8601 date or datetime | now | This endpoint has no range mode; it always answers for one instant. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
ambiguous | no | first | last | error | first | How 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. |
nonexistent | no | error | shift_forward | shift_back | error | How 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. A bare date names a day, so its default is shift_forward: where the clocks change at midnight, the day starts when the clock resumes. |
include | no | comma-separated: showpieces, ladder, sky_conditions | showpieces | showpieces 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. |
refraction | no | normal | none | normal | normal 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_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json, csv, txt. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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-10-30T20:48: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 catalog 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.
Aurora now: live Kp and storm level, the three-day Kp forecast, the solar wind and the auroral oval from NOAA SWPC; with a location, whether the oval is overhead or on your horizon, whether it is dark, and the forecast Kp over tonight's dark hours.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
lat | no | decimal 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. |
lon | no | decimal 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_m | no | metres, -500 to 9000 | 0 | Observer height above mean sea level. Raises the local horizon geometrically; sea-horizon dip is not modeled (the figure is published in meta.horizon so you can apply it yourself). Aliases: elevation, height. |
place | no | string: 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_strategy | no | best | error | best | How 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. |
tz | no | IANA zone name (Europe/London) or UTC | UTC, or the resolved place's zone | The time zone for a date-only or offset-less at/start/end value, and for local-time fields in the response. Alias: timezone. Abbreviations (EST) are refused; the single-word place names of the tz database (Japan, Singapore) are read as their zones. tz=auto is reserved and refused. |
time_format | no | iso | unix | iso | How instants are rendered: ISO 8601 strings, or Unix seconds. Alias: timeformat. |
format | no | enum | json | Response format. This endpoint serves: json. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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: now_relative
This request is anchored to "now" (no at/start was sent), so the answer is recomputed on a short clock and cached only briefly. Cache-Control: public, max-age=30. At the edge (Netlify-CDN-Cache-Control): public, durable, s-maxage=60, stale-while-revalidate=3600, stale-if-error=86400.
Example request
GET https://www.cyclecalcs.com/v2/aurora?lat=60.1699&lon=24.9384&elevation_m=20&tz=Europe/Helsinki
Example response (real, captured live)
{
"endpoint": "/v2/aurora",
"computed_at": "2026-10-17T21:00:00.000Z",
"query": {
"at": "2026-10-17T21:00:00.000Z",
"mode": "instant",
"resolved_from": "default_now",
"is_now": true,
"latitude": 60.1699,
"longitude": 24.9384,
"elevation_m": 20,
"tz": "Europe/Helsinki",
"utc_offset_seconds": 10800,
"utc_offset_iso": "+03:00",
"is_dst": true,
"timezone_source": "parameter",
"time_format": "iso",
"format": "json",
"shape": "nested",
"precision": 6,
"pretty": 1,
"verbosity": "full",
"ignored": []
},
"data": {
"activity": {
"kp": 2.33,
"kp_source": "estimated_one_minute",
"kp_at": "2026-10-17T23:51:00.000+03:00",
"g_scale": null,
"storm_level": "none",
"three_hour_kp": 2.33,
"three_hour_kind": "estimated",
"last_observed_block": {
"start": "2026-10-17T18:00:00.000+03:00",
"end": "2026-10-17T21:00:00.000+03:00",
"kp": 2.67
}
},
"forecast": {
"blocks": [
{
"start": "2026-10-18T00:00:00.000+03:00",
"end": "2026-10-18T03:00:00.000+03:00",
"kp": 2.33,
"kind": "estimated",
"g_scale": null
},
{
"start": "2026-10-18T03:00:00.000+03:00",
"end": "2026-10-18T06:00:00.000+03:00",
"kp": 1.67,
"kind": "predicted",
"g_scale": null
},
{
"start": "2026-10-18T06:00:00.000+03:00",
"end": "2026-10-18T09:00:00.000+03:00",
"kp": 1.67,
"kind": "predicted",
"g_scale": null
},
{
"start": "2026-10-18T09:00:00.000+03:00",
"end": "2026-10-18T12:00:00.000+03:00",
"kp": 1.33,
"kind": "predicted",
"g_scale": null
},
"... 14 more rows omitted for length ..."
],
"peak": {
"start": "2026-10-19T21:00:00.000+03:00",
"end": "2026-10-20T00:00:00.000+03:00",
"kp": 4,
"g_scale": null
}
},
"solar_wind": {
"speed_km_s": 476,
"bt_nanotesla": 6,
"bz_nanotesla": 3,
"bz_southward": false,
"observed_at": "2026-10-17T23:49:00.000+03:00"
},
"oval": {
"observed_at": "2026-10-17T23:49:00.000+03:00",
"forecast_for": "2026-10-18T00:47:00.000+03:00",
"north_max_percent": 14,
"south_max_percent": 15,
"north_lowest_edge_latitude_deg": 66,
"south_lowest_edge_latitude_deg": -55
},
"location": {
"geomagnetic_latitude_deg": 57.742239,
"sun_altitude_deg": -37.868699,
"sky": "night",
"moon_altitude_deg": -17.291067,
"moon_illuminated_fraction": 0.425757,
"probability_here_percent": 0,
"oval_edge_latitude_deg": 66,
"oval_edge_distance_km": 648,
"oval_edge_direction": "north",
"lower_border_elevation_deg": 6.626208,
"red_top_elevation_deg": 17.786039,
"reach": "on_horizon",
"verdict": "possible_low",
"statement": "Kp is 2.33. The oval's edge is about 648 km to the north, so aurora could show low on that horizon. Find a dark view that way.",
"tonight": {
"dark_from": "2026-10-18T00:00:00.000+03:00",
"dark_until": "2026-10-18T06:40:00.000+03:00",
"dark_until_is_scan_end": false,
"dark_now": true,
"max_forecast_kp": 2.33,
"max_forecast_block": {
"start": "2026-10-18T00:00:00.000+03:00",
"end": "2026-10-18T03:00:00.000+03:00",
"g_scale": null
}
}
},
"model": {
"not_a_guarantee": true,
"oval_edge_percent": 10,
"likely_percent": 50,
"lower_border_height_km": 110,
"red_top_height_km": 250,
"minimum_elevation_deg": 5,
"dark_sun_altitude_deg": -12,
"scan_hours": 24,
"scan_step_minutes": 10,
"feed_stale_minutes": 60,
"geomagnetic_pole": {
"latitude_deg": 80.79,
"longitude_deg": -72.76,
"epoch": 2025,
"model": "WMM2025 centered dipole"
},
"geometry": "Spherical Earth of radius 6371 km, no refraction. The oval edge is searched along the observer's own meridian only, so an oval that bulges toward you east or west of it is not seen by this test.",
"clouds": "Not modeled. This API has no weather data."
}
},
"warnings": [
{
"code": "aurora_estimate",
"message": "This verdict is a planning estimate from the OVATION nowcast and the Kp forecast. It cannot promise a display, and clouds are not known to this API.",
"field": "location.verdict",
"value": "possible_low"
}
],
"links": {
"self": "https://www.cyclecalcs.com/v2/aurora?elevation_m=20&lat=60.1699&lon=24.9384&tz=Europe%2FHelsinki",
"docs": "https://www.cyclecalcs.com/api/reference.html#aurora",
"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#aurora"
}
Some arrays are shortened above for length; the live response returns every row.
Places
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.
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
q | no | string, 2+ characters | (required for a name search) | A place name to search for, matched against the GeoNames-derived index, including each place's alternate names (meta.method.other_names): "Praha" finds Prague. One of q, id, or lat and lon (reverse lookup) is required. |
id | no | a 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. |
lat | no | decimal degrees, -90 to 90 | (none) | Together with lon, performs a reverse lookup: the nearest places to this point, within radius_km. |
lon | no | decimal degrees, -180 to 180 | (none) | Together with lat, performs a reverse lookup. Sent together with lat, or neither. |
country | no | ISO 3166-1 alpha-2 code, e.g. GB | (no filter) | Restricts a name search to one country. |
region | no | string | (no filter) | Restricts a name search to one admin region (state/province) within a country. |
limit | no | integer, 1 to 25 | 6 | Maximum number of place rows to return. This is a RESULT COUNT, unlike limit on every other endpoint, which pages a series. |
min_population | no | integer, 0 to 40,000,000 | 0 | Excludes 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_km | no | kilometres, 1 to 2000 | 250 | Search radius for a reverse lookup (lat/lon). Has no effect on a name search. |
format | no | enum | json | Response format. This endpoint serves: json, csv. |
fields | no | comma-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. |
shape | no | nested | flat | nested | nested returns the natural object/array tree. flat returns data as one level of dot-path keys, for easy tabular consumption while staying JSON. |
precision | no | integer, 0 to 9 | 6 | Decimal places for numeric output. |
pretty | no | 0 | 1 | 1 | 1 pretty-prints the JSON with two-space indentation; 0 emits it compact, on one line. |
verbosity | no | full | compact | full | compact 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_format | no | iso | unix | iso | How 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": 13,
"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": 70797,
"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 13 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": "13"
},
{
"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 13 matches to 5.",
"field": "matched",
"value": "13"
}
],
"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.
All API documentation
- Developer hub: What to build and where every tool lives: API, MCP server, widgets.
- Overview and playground: What the API answers, the plans, and a live try-it panel.
- Endpoint reference: Every route, its parameters and a real captured response.
- Keys and plans: How to get a key, what each plan raises, and how quotas are counted.
- Error codes: The registered problem documents, one entry per code.
- Accuracy and residuals: Measured error against reference ephemerides, and the conventions every response states.
- Versioning policy: What is frozen, what may change, and how a change is announced.
- Status and limits: Rate windows, range caps and the current service position.
- MCP server: The same engine as tools an AI assistant can call directly.
- Version 1 (frozen): The original seven endpoints, still served and never changing shape.
- Terms of use: What you may do with the answers, including in commercial products.