The catalog, one endpoint away

V1 · LIVE

223 448 objects over 11 endpoints — as JSON, as a VOTable your telescope software already opens, or as CSV. No key, no account, no signup. CORS is open to every origin, so a browser can call this directly. 60 requests per minute per client; every record names the catalog it came from, and that catalog’s licence governs it. Copying the whole thing is a download, not a crawl.

[

START HERE

] ONE REAL RECORD
$ curl https://spacecatalog.org/api/v1/objects/sgr-a

{
  "slug": "sgr-a",
  "name": "Sagittarius A*",
  "class": "supermassive_black_hole",
  "category": "black_hole",
  "mass_kg": 8.251900963396911e+36,
  "mass_msun": 4150000,
  "distance_ly": 26673,
  "constellation": "Sagittarius",
  "discovered": "1974",
  "designations": [
    "Sagittarius A*",
    "Sgr A*"
  ],
  "source": "curated",
  "…": "description, properties, ra_deg, dec_deg, updated_at"
}

OPENAPI 3.1 SPEC →

[

ENDPOINTS

] 11 ROUTES
GET/api/v1/objectsList objects

The whole catalog, filtered and paged. Filters compose: category, constellation, class, magnitude range and the curated flag can all be combined. Results are brightest first unless you say otherwise, and objects without a recorded magnitude sort last in either direction.

PARAMTYPEDEFAULTNOTES
categorystringOne of the eleven top-level classes.star · planet · moon · exoplanet · galaxy · nebula · cluster · black_hole · asteroid · comet · other
constellationstringIAU constellation. Spacing and case are ignored: 'Ursa Major', 'ursa-major' and 'ursamajor' are the same region.
typestringExact `object_type` string, as returned in the records themselves — 'Spiral galaxy Sb', 'M2Ib red supergiant'.
mag_minnumber -30…40Faintest bound: only objects at or above this magnitude.
mag_maxnumber -30…40Brightest bound: only objects at or below this magnitude. `mag_max=6` is roughly the naked-eye sky.
featuredbooleanRestrict to the curated entries — the ones with hand-written descriptions.
sortstringmagField to order by.name · type · mass · distance · mag · constellation
orderstringascSort direction.asc · desc
limitinteger 1…20025Records per page.
offsetinteger 0…1000000Records to skip. Capped at 100,000 — past that you want a bulk export, not a page.
epochstringCarry every position in the answer forward to a date. `now`, a Julian year (`2026.5`, `J2026.5`), or an ISO 8601 date, anywhere between 1500 and 2500 — a published proper motion is a velocity measured over a few years, and carrying one much further is extrapolation with no measurement under it. The stored `ra_deg` and `dec_deg` stay J2000; the propagated pair arrives beside them as `epoch_ra_deg` and `epoch_dec_deg`, and an `epoch` block on the response says which terms went into them and which did not.
framestringicrsWhich frame `epoch` propagates into, and meaningless without it. `icrs` applies the object's own proper motion and nothing else — a position epoch, the sense Gaia uses, and the one to cross-match in. `date` also turns the coordinate grid to the true equator and equinox of that instant — precession plus nutation — which is what a telescope mount expects and is about fifty arcseconds a year larger.icrs · date
formatstringjsonResponse shape. `json` is the full record, including the parts a table has no room for. `votable` is IVOA VOTable 1.4 with units and UCDs, which TOPCAT and Aladin open directly; `csv` is the same columns under the same names. Both flat forms drop the nested parts of a record — an object's properties, designations and source block — and keep the observable quantities.json · votable · csv
TRY IT · /api/v1/objects?category=galaxy&mag_max=9&limit=5
GET/api/v1/objects/{slug}Get one object

Everything known about a single object: the record itself, its source-specific properties, every cross-designation it answers to, and the catalog it came from with that catalog's license. Add `epoch` to be told where it is at a date rather than only where it was measured in 2000 — and, for a variable star, where in its cycle it is at that same instant and when its next maximum or minimum falls.

PARAMTYPEDEFAULTNOTES
slug *stringThe object's stable identifier, as it appears in its site URL.
epochstringCarry every position in the answer forward to a date. `now`, a Julian year (`2026.5`, `J2026.5`), or an ISO 8601 date, anywhere between 1500 and 2500 — a published proper motion is a velocity measured over a few years, and carrying one much further is extrapolation with no measurement under it. The stored `ra_deg` and `dec_deg` stay J2000; the propagated pair arrives beside them as `epoch_ra_deg` and `epoch_dec_deg`, and an `epoch` block on the response says which terms went into them and which did not.
framestringicrsWhich frame `epoch` propagates into, and meaningless without it. `icrs` applies the object's own proper motion and nothing else — a position epoch, the sense Gaia uses, and the one to cross-match in. `date` also turns the coordinate grid to the true equator and equinox of that instant — precession plus nutation — which is what a telescope mount expects and is about fifty arcseconds a year larger.icrs · date
TRY IT · /api/v1/objects/barnard-s-star?epoch=now&frame=date
GET/api/v1/searchSearch

Full-text over names and designations, with trigram similarity as the fallback for near misses. A typed designation wins over everything else, so 'NGC 224' resolves to the Andromeda Galaxy rather than to a page of its own. Each result carries the designation that matched.

PARAMTYPEDEFAULTNOTES
q *stringWhat to look for: a name, a designation, or something close to one.
categorystringRestrict results to one class.star · planet · moon · exoplanet · galaxy · nebula · cluster · black_hole · asteroid · comet · other
limitinteger 1…10020Maximum results.
formatstringjsonResponse shape. `json` is the full record, including the parts a table has no room for. `votable` is IVOA VOTable 1.4 with units and UCDs, which TOPCAT and Aladin open directly; `csv` is the same columns under the same names. Both flat forms drop the nested parts of a record — an object's properties, designations and source block — and keep the observable quantities.json · votable · csv
TRY IT · /api/v1/search?q=ngc%20224
GET/api/v1/visibleWhat is up right now

Objects above the horizon from a given position, brightest first as seen from the ground rather than above the atmosphere — `mag_at_altitude` carries the extinction of the air in the way and is what the list is ordered on, so an object low in the sky sorts below an equally bright one overhead. With altitude, azimuth and a compass bearing. Objects with a fixed position come from the catalog, carried forward by their own proper motion and precessed from J2000 to the equator of the requested instant; the Sun, Moon and planets are computed from an ephemeris, so they carry their real current magnitude and distance. Every position in the list is therefore of date, in one frame. The response also reports the Sun's altitude and what that makes of the sky — an object at 40° in daylight is up but not visible.

PARAMTYPEDEFAULTNOTES
lat *number -90…90Observer latitude in degrees, north positive.
lon *number -180…180Observer longitude in degrees, east positive.
timestringISO 8601 instant. Defaults to now, and is rounded to five minutes.
min_altnumber -90…9015Altitude floor in degrees. The default of 15 skips the haze and obstructions near the horizon.
elevation_mnumber -500…90000Observer height above sea level, in metres.
limitinteger 1…10025Maximum objects returned.
formatstringjsonResponse shape. `json` is the full record, including the parts a table has no room for. `votable` is IVOA VOTable 1.4 with units and UCDs, which TOPCAT and Aladin open directly; `csv` is the same columns under the same names. Both flat forms drop the nested parts of a record — an object's properties, designations and source block — and keep the observable quantities.json · votable · csv
TRY IT · /api/v1/visible?lat=19.82&lon=-155.47&limit=10
GET/api/v1/eventsThe dated sky

Everything that happens on a date rather than at a place: eclipses, meteor showers, the Moon's quarters, close pairings of the bright planets, oppositions and greatest elongations, the equinoxes and solstices, and near-Earth asteroid flybys. Every instant here is the same fact everywhere on Earth — a full moon is full for all of us at once — which is what separates this endpoint from `/api/v1/visible`, where the answer depends entirely on where you are standing. Seven of the eight kinds are solved from an ephemeris when you ask, so there is no stored list to go stale; the eighth cannot be, because a newly discovered asteroid has no orbit until somebody measures one, and the response says in `meta.close_approaches` which window that part covers and when it was last refreshed. Positions are geocentric ICRS/J2000: the body at the instant, or a shower's radiant. The `circumstances` block carries whatever numbers the kind has — a separation, an obscuration, a rate, a miss distance — and omits what it does not.

PARAMTYPEDEFAULTNOTES
fromstringISO 8601 instant to start at. Defaults to now.
tostringISO 8601 instant to stop at, at most two years after `from`. Defaults to eighteen months out, which is the horizon the calendar page draws.
kindstringRestrict to one kind of event. Repeatable — `kind=eclipse&kind=flyby` asks for both.moon · season · opposition · elongation · conjunction · eclipse · shower · flyby
limitinteger 1…500500Maximum events returned.
formatstringjsonResponse shape. `json` is the full record, including the parts a table has no room for. `votable` is IVOA VOTable 1.4 with units and UCDs, which TOPCAT and Aladin open directly; `csv` is the same columns under the same names. Both flat forms drop the nested parts of a record — an object's properties, designations and source block — and keep the observable quantities.json · votable · csv
TRY IT · /api/v1/events?from=2026-01-01&to=2026-12-31&kind=eclipse
GET/api/v1/ephemerisOne target, one night

Rise, transit and set for a single object from a single place, with the altitude and air-mass curve of the whole night beside them and the Moon on every row of it. `/api/v1/visible` answers what is above the horizon at an instant; this answers the question an observing run is planned from, which is whether *this* object is worth the drive on *this* night — how many hours it is both high enough and dark enough, when in those hours it is highest, and how close the Moon will be while they pass. The target is any identifier this catalog resolves, or a bare position for something no catalogue holds yet. Rise and set are the almanac definition, the upper limb crossing a horizon lifted by 34 arcminutes of refraction scaled to the density of the air at the observer's elevation; every other altitude in the answer, including the curve and the thresholds cut out of it, is the geometric altitude of the body's centre. Those two differ by about half a degree and the response reports the horizon it used rather than leaving the reader to discover the difference. The observable hours are solved for, not counted off the samples, so they do not change when `step_min` does.

PARAMTYPEDEFAULTNOTES
objectstringWhat to observe: a slug, a designation, or a survey identifier — 'm31', 'Barnard’s Star', 'NGC 224', 'Gaia DR3 66526127137440128'. Mutually exclusive with `ra` and `dec`. An asteroid or a comet is refused rather than guessed at: this catalog stores no orbital elements for them, and only the Sun, the Moon and the eight planets have an ephemeris behind them.
ranumber 0…360Target right ascension in ICRS degrees, for something the catalog does not hold — a transient, a field centre, a target of opportunity. Requires `dec`.
decnumber -90…90Target declination in ICRS degrees. Requires `ra`.
lat *number -90…90Observer latitude in degrees, north positive.
lon *number -180…180Observer longitude in degrees, east positive.
elevation_mnumber -500…90000Observer height above sea level, in metres. It is not cosmetic: thinner air refracts less, so the same star rises four minutes later from the top of Mauna Kea than the sea-level figure says.
datestringWhich night. A calendar date is the night that *begins* on it where the observer is, worked out from their longitude rather than from a timezone table; a full ISO 8601 instant is taken as given, and the night answered is the one whose solar midnight comes next. Defaults to tonight.
min_altnumber -20…8920The altitude an observation counts from, in degrees. The default of 20 is the floor the site's own observing list is built at, so the two agree about what is up.
windowstringnightWhat the night is: `night` is sunset to sunrise, `24h` is a whole turn of the Earth centred on solar midnight. `24h` is forced where there is no sunset to bound the other one — a polar summer, a polar winter, or the Sun itself as the target.night · 24h
step_mininteger 1…12010Minutes between samples of the curve. Ten resolves an air-mass plot; one resolves a grazing occultation. The event times and the observable hours are solved for and do not depend on it.
formatstringjsonResponse shape. `json` is the full record, including the parts a table has no room for. `votable` is IVOA VOTable 1.4 with units and UCDs, which TOPCAT and Aladin open directly; `csv` is the same columns under the same names. Both flat forms drop the nested parts of a record — an object's properties, designations and source block — and keep the observable quantities.json · votable · csv
TRY IT · /api/v1/ephemeris?object=m31&lat=19.82&lon=-155.47&date=2026-10-15
GET/api/v1/resolveResolve any identifier

What is this identifier? Answers across both tiers at once: a catalogued object where there is one, a survey source otherwise. `tier` says which — 'catalogued' carries a full object record under `object`, 'survey' carries a source record under `source`. Catalogued objects win any tie, because a named object with prose and cross-designations is the better answer than the survey row it was merged from. Every spelling resolves: 'M31', 'm 31', 'NGC 224', 'HD 48915', 'Gaia DR3 66526127137440128' and 'GLADE+ 1' are all identifiers this catalog can place.

PARAMTYPEDEFAULTNOTES
id *stringA designation, a slug, or a survey identifier. 120 characters or fewer.
TRY IT · /api/v1/resolve?id=NGC%20224
GET/api/v1/sourcesSurvey sources by position

Every survey source within a cone, nearest first. This is the tier the catalog stores and serves but does not index: rows of Gaia DR3 and GLADE+ as those surveys published them, with no article and no page in any search engine — so position and brightness are the only questions they can be asked, and the only ones this endpoint takes. One catalog per request: Gaia magnitudes are in G and GLADE+ magnitudes are in B, and mixing them would put two bands in one column ordered against each other. The radius is capped at one degree, which is not about query cost but about the answer: a degree of the galactic plane holds tens of thousands of Gaia sources brighter than G = 15. `truncated` says when the scan hit its own cap, which it takes brightest-first. The positions are each survey's own: GLADE+ publishes ICRS positions of galaxies, which do not move perceptibly, and Gaia DR3 publishes at epoch J2016.0 — so a cone centre taken from a J2000 catalogue is sixteen years of proper motion away from where Gaia sees the same star, which is a tenth of an arcsecond for most of them and minutes of arc for the fastest.

PARAMTYPEDEFAULTNOTES
ra *number 0…360Cone centre, ICRS right ascension in degrees.
dec *number -90…90Cone centre, ICRS declination in degrees.
radiusnumber 0.0001…10.1Cone radius in degrees.
catalogstringgaia-dr3Which survey to search: 'gaia-dr3' (stars, magnitudes in Gaia G) or 'glade' (galaxies and quasars, magnitudes in B).
mag_maxnumber -30…25Faintest magnitude to return, in whichever band the chosen catalog publishes — Gaia G, a broad white-light band that is not V, or GLADE+'s photographic B. `meta.sort_band` says which was applied.
limitinteger 1…50050Maximum sources returned.
formatstringjsonResponse shape. `json` is the full record, including the parts a table has no room for. `votable` is IVOA VOTable 1.4 with units and UCDs, which TOPCAT and Aladin open directly; `csv` is the same columns under the same names. Both flat forms drop the nested parts of a record — an object's properties, designations and source block — and keep the observable quantities.json · votable · csv
TRY IT · /api/v1/sources?ra=57.29&dec=24.05&radius=0.1&limit=5
GET/api/v1/sources/{id}Get one survey source

One row of a sky survey, by its own identifier. Every spelling is accepted — 'gaia-dr3-66526127137440128', 'Gaia DR3 66526127137440128', 'Gaia+DR3+66526127137440128', 'glade-1', 'GLADE+ 1' — because a caller quoting the survey's printed form is quoting it correctly. `kind` says which record shape came back, star or galaxy, before any field is read. `in_catalog` says whether this row is also held as a catalogued object, in which case that object's page is the better answer.

PARAMTYPEDEFAULTNOTES
id *stringThe survey identifier, in any spelling.
TRY IT · /api/v1/sources/gaia-dr3-66526127137440128
GET/api/v1/scs/{table}Simple Cone Search (IVOA)

The standard cone search, so the catalog can be opened in the tools astronomers already have: paste the base URL into TOPCAT or Aladin and the answer arrives as a layer on the sky. One service per table, which is what the standard means by one — 'objects' is the catalogued tier, 'gaia-dr3' and 'glade' are the surveys beneath it — because a cone search resource answers for a single table with a single set of columns. The response is always a VOTable, with the identifier, right ascension and declination carrying the UCDs a client is promised. Parameter names are matched without regard to case, and an invalid request comes back as HTTP 200 carrying a VOTable whose QUERY_STATUS is ERROR, which is where a conforming client looks for the reason. Where the scan reached its own cap before the cone was exhausted, an OVERFLOW status follows the table. The catalogued table's positions are J2000; the survey tables carry each survey's own epoch, and Gaia DR3's is J2016.0.

PARAMTYPEDEFAULTNOTES
table *stringWhich table to search: 'objects' (the catalog, cones up to 10°), 'gaia-dr3' or 'glade' (the survey tier, cones up to 1°).
RA *number 0…360Cone centre, ICRS right ascension in decimal degrees.
DEC *number -90…90Cone centre, ICRS declination in decimal degrees.
SR *number 0…10Search radius in decimal degrees. The ceiling is the table's: 10 for the catalog, 1 for a survey.
VERBinteger 1…32How many columns to return: 1 the identifier, the position and the separation; 2 the set worth having; 3 every column the table holds. An unrecognised value is treated as 2 rather than refused.
MAXRECinteger 1…10000Maximum rows. Defaults to 1,000 for the catalog and 500 for a survey; an answer cut short says so with an OVERFLOW status after the table.
TRY IT · /api/v1/scs/objects?RA=10.68&DEC=41.27&SR=0.5&VERB=2
POST/api/v1/xmatchCross-match a target list

Send a list of positions and get back the nearest row to each of them, in the order they were sent. This is the question a cone search cannot be asked: an observing run produces a file of positions, and asking about them one at a time is a request per line. Every answer says how contested it was — how many rows fell inside the tolerance, how far the runner-up was, and how many unrelated rows the sky right there would be expected to put inside it anyway — because the nearest row within a few arcseconds is an identification only where the field is empty enough for it to be one. Targets that matched nothing keep their place in the list, so the answer still lines up with the file it came from. The body is JSON or a table; positions are decimal degrees, or sexagesimal, in which case the right ascension is in hours.

PARAMTYPEDEFAULTNOTES
tablestringobjectsWhich table to match against: 'objects' (the catalog, tolerances up to 300″), 'gaia-dr3' or 'glade' (the survey tier, up to 60″).
radius_arcsecnumber 0.01…3005Match tolerance in arcseconds. The default of 5 is the usual choice for optical positions; widen it for a list from a low-resolution instrument, and read `matches_within_radius` when you do.
epochstringCarry the catalogue's positions to the epoch the target list is on before comparing, rather than after. A target list from a modern instrument is on that instrument's epoch and every catalogued position here is J2000, so a fast star can sit arcseconds from its own counterpart for no other reason. Takes `now`, a Julian year (`J2016.0` is Gaia's) or an ISO 8601 date. Only the catalogued tier can be propagated: the survey tables hold positions and no proper motions, and their rows are matched on the epoch they were published at.
framestringicrsWhich frame `epoch` propagates into, and meaningless without it. `icrs` applies the object's own proper motion and nothing else — a position epoch, the sense Gaia uses, and the one to cross-match in. `date` also turns the coordinate grid to the true equator and equinox of that instant — precession plus nutation — which is what a telescope mount expects and is about fifty arcseconds a year larger.icrs · date
formatstringjsonResponse shape. `json` is the full record, including the parts a table has no room for. `votable` is IVOA VOTable 1.4 with units and UCDs, which TOPCAT and Aladin open directly; `csv` is the same columns under the same names. Both flat forms drop the nested parts of a record — an object's properties, designations and source block — and keep the observable quantities.json · votable · csv

The target list, as JSON — `{"targets": [{"id": "a", "ra": 10.6847, "dec": 41.2687}]}`, or a bare array of those — or as a table with one target per line. A table may be comma, tab, semicolon or whitespace separated; a first line naming its columns is read for `ra`, `dec` and an identifier, and without one a row is `ra,dec` or `id,ra,dec`. Lines beginning with `#` are comments. A row that cannot be read is an error naming the row rather than a row quietly dropped, because a list of answers only lines up with the list of questions if nothing went missing between them. 500 targets per request.

$ curl -X POST "https://spacecatalog.org/api/v1/xmatch?table=objects&radius_arcsec=5" \
    -H 'content-type: application/json' --data-binary @targets.json
[

IN YOUR OWN TOOLS

] VOTABLE · CSV · CONE SEARCH

Every list endpoint answers in three shapes. format=json is the full record. format=votable is IVOA VOTable 1.4, which TOPCAT, Aladin and anything else that reads the standard will open with the units and the UCDs intact — the position columns are tagged pos.eq.ra;meta.main and pos.eq.dec;meta.main, so a table lands on the sky without being told which column is which. format=csv is the same columns under the same names, for everything else. Both flat forms drop the parts of a record a table has no room for — an object’s properties, its cross-designations, the source block — and keep the observable quantities.

$ curl -o galaxies.vot \
    "https://spacecatalog.org/api/v1/objects?category=galaxy&mag_max=9&limit=200&format=votable"

For the tools that would rather ask than be handed a file, three Simple Cone Search services. Paste a base URL into a cone-search box, give it a position and a radius, and the answer is a layer on the sky. One service per table, because that is what the standard means by one: the catalog, and each of the two surveys underneath it.

BASE URLMAX SRWHAT IS IN IT
https://spacecatalog.org/api/v1/scs/objects?10°The catalog: merged, named, cross-identified records with distances and provenance.
https://spacecatalog.org/api/v1/scs/gaia-dr3?Gaia DR3 as published — position, G magnitude, colour and parallax, nothing merged.
https://spacecatalog.org/api/v1/scs/glade?GLADE+ galaxies and quasars — B and K magnitudes, CMB-frame redshift, distance.

RA, DEC and SR in decimal degrees, case-insensitive; VERB=1 for the identifier and the position alone, 3 for every column. A request the service cannot answer comes back as a VOTable saying why, because that is where a cone-search client looks — and where the answer was cut short, an OVERFLOW status follows the table rather than letting a partial result pass for a complete one.

[

POSITIONS AT A DATE

] EPOCH PROPAGATION

Every catalogued position here is J2000, because that is the epoch its upstreams publish. It is also where the sky was a quarter of a century ago: since then the coordinate grid has turned by more than twenty arcminutes, and the fastest stars have crossed several arcminutes of their own. Add epoch to either object endpoint and each answer carries the propagated position as well as the stored one.

$ curl "https://spacecatalog.org/api/v1/objects/barnard-s-star?epoch=now&frame=date"

epoch takes now, a Julian year J2026.5, or an ISO 8601 date. frame=icrs, the default, applies the object’s own proper motion and nothing else — that is what an epoch is, and it is the position to cross-match against Gaia with. frame=date also turns the grid to the true equator and equinox of that instant — precession plus nutation — which is what a mount expects and is about fifty arcseconds a year larger. The two are different corrections to different things: one moves the object, the other moves the grid under it.

Nothing is overwritten. ra_deg and dec_deg are the catalogued J2000 pair in every response; epoch_ra_deg and epoch_dec_deg arrive beside them, and epoch_moved_arcsec is how far the object itself travelled — proper motion alone, so it reads the same in either frame. The published motions come with it, as pm_ra_mas_yr (that is µα cos δ, an angle on the sky) and pm_dec_mas_yr, so the arithmetic can be redone and checked. An object with no measured motion comes back where the catalog has it rather than at an invented zero, and a body with no fixed position at all is not propagated — a planet, a moon or a comet has an ephemeris rather than a coordinate, which is what /api/v1/visible answers with.

Left out: parallax, the radial-velocity perspective term, aberration and refraction. Each is smaller than an arcminute, which is also about as well as the catalogued positions themselves are known. Every response that propagated anything carries an epoch block spelling that out, because “of date” means at least four different things depending on which terms are in it.

[

YOUR LIST, NOT ONE POSITION

] CROSS-MATCH

An observing run does not produce a position. It produces a file of them, and for every line the same question: what is this, and how far off is it. POST /api/v1/xmatch takes the whole file and answers it once — up to 500 targets, matched against the catalog or against either survey underneath it, with the answers in the order the targets were sent so the result joins straight back onto the file it came from. A target that matched nothing keeps its line rather than disappearing out of the middle of your spreadsheet.

$ curl -X POST "https://spacecatalog.org/api/v1/xmatch?radius_arcsec=3&format=csv" \
    --data-binary @targets.csv

target_row,target_id,target_ra_deg,target_dec_deg,separation_arcsec,…
1,field-1,10.6847,41.2687,1.42,…

The body is JSON — {"targets": [{"id": "a", "ra": 10.6847, "dec": 41.2687}]} — or a table with one target per line, comma, tab, semicolon or whitespace separated. A first line naming its columns is read for a right ascension, a declination and an identifier under the spellings the archives use, and without one a row is ra,dec or id,ra,dec. Positions may be decimal or sexagesimal, and which one decides the unit: 10.6847 is degrees, 00 42 44.3 and 00:42:44.3 are hours. That is the convention every catalog uses and it is also the most expensive thing here to get wrong, so a row that cannot be read comes back as an error naming the row rather than as a row quietly dropped — a list of answers only lines up with the list of questions if nothing went missing in between.

The nearest row is not an identification, and every match says how contested it was. matches_within_radius is how many rows fell inside the tolerance, next_separation_arcsec is how far the runner-up was, and chance_alignments is how many unrelated rows the sky right there would be expected to put inside the tolerance anyway — counted from the surface density measured around that target rather than from the catalog’s average, because a degree of the galactic plane holds two orders of magnitude more Gaia sources than a degree at the pole and a false-match rate quoted for the average is wrong in both places. Against the catalog it is essentially always zero; against Gaia at three arcseconds in a crowded field it is not, and that difference is the whole question.

epoch moves the catalog to the epoch your list is on before the comparison rather than after it, which matters for exactly the stars a cross-match is most likely to get wrong: catalogued positions here are J2000, and a star with a large proper motion can sit several arcseconds from its own counterpart in a list taken last year. The survey tables cannot be propagated — they hold positions and no proper motions — and they are matched on the epoch they were published at, which for Gaia DR3 is J2016.0 and not J2000.

[

ONE TARGET, ONE NIGHT

] RISE · TRANSIT · SET · AIRMASS

/api/v1/visible answers what is above the horizon at an instant. The question an observing run is planned from is the other one: this object, this night — is it up at all, how many of the dark hours is it high enough to be worth pointing at, when in those hours is it highest, and where is the Moon going to be while they pass. /api/v1/ephemeris answers that for one target: rise, transit and set, the altitude and air-mass curve of the whole night, and the Moon on every row of it.

$ curl "https://spacecatalog.org/api/v1/ephemeris?object=m31&lat=19.82&lon=-155.47&date=2026-10-15"

"events":       { "rise": …, "transit": …, "transit_altitude_deg": 78.2, … }
"observability":{ "hours_observable": 7.4, "best": { "airmass": 1.02, … } }
"moon":         { "phase_name": "waning crescent", "separation_deg": 96.3, … }
"track":        [ { "time": …, "altitude_deg": …, "airmass": …, "sky": … }, … ]

The target is anything this catalog resolves — a slug, a designation, a Gaia or GLADE+ identifier — or a bare ra and dec for the transient that is not in any catalogue yet, which is most of what a telescope actually points at. A star’s own proper motion is applied before the night is computed rather than after, and the propagated position comes back with the answer. format=csv and format=votable return the curve, because an air-mass curve is a table before it is a picture and TOPCAT will plot it from those rows unaided.

Two horizons, and the response says which is which. Rise and set are the almanac definition — the upper limb crossing a horizon lifted by 34 arcminutes of refraction — and that refraction is scaled to the density of the air the observer is actually under, so the same star rises measurably later from the top of a mountain than the sea-level figure claims. Every other altitude here, including the curve and the thresholds cut out of it, is the geometric altitude of the body’s centre with no refraction in it. The two differ by about half a degree, which is why the curve reads slightly below zero at the reported rise time, and events.horizon_altitude_deg is the number that reconciles them.

hours_observable is solved for, not counted off the samples: the moments the target crosses the altitude floor and the moments the sky crosses eighteen degrees of twilight are each found to the second and then intersected, so the figure does not move when step_min does. And a night that has no sunset in it is answered rather than refused — the window becomes the whole day, and the response distinguishes a Sun that never sets from one that never rises, which arrive from the same search as the same missing sunset.

[

WHEN, NOT WHERE

] THE DATED SKY

Every other endpoint here answers a question about a place — where an object is, what is above your horizon, when it rises for you. /api/v1/events answers the one that has no place in it. A full moon is full for all of us at once; an eclipse peaks at one instant; a meteor shower maximises when the Earth reaches a particular point in its orbit. Eight kinds — eclipses, showers, the Moon’s quarters, close pairings of the bright planets, oppositions, greatest elongations, the equinoxes and solstices, and near-Earth flybys — earliest first, over any window up to two years.

$ curl "https://spacecatalog.org/api/v1/events?from=2026-01-01&to=2026-12-31&kind=eclipse"

"time":          "2026-08-12T17:46:00.000Z"
"title":         "Total solar eclipse"
"position":      { "ra_deg": 145.9, "dec_deg": 14.8 }
"circumstances": { "obscuration": 1, "greatest_at": { "lat_deg": 65.2, … } }

Seven of the eight kinds are solved from an ephemeris at the moment you ask, so there is no stored list to run out and no year that quietly ends. The eighth cannot be: a rock discovered last month has no orbit until somebody measures one, so the close approaches are what JPL published when this was last refreshed. meta.close_approaches carries that date and the date the list runs to, because the end of a window and an empty stretch of sky look identical from the outside and only one of them is true.

circumstances holds whatever numbers the kind has and omits what it does not — a separation for a pairing, an obscuration and the latitude and longitude of greatest eclipse for a solar one, a rate and an entry speed for a shower, a miss distance in lunar distances for a flyby. format=csv and format=votable flatten all of them into columns, one per circumstance, mostly empty per row and filterable in one pass, which is the shape a table wants even where a record would nest. position is geocentric ICRS/J2000 — the body at that instant, or a shower’s radiant — and is absent where an event has no one place in the sky, as an equinox does not.

The same events are also served as a calendar file, which is the format most people actually want them in: /calendar.ics is the next twelve months as timed UTC entries, and it takes the calendar page’s own filters — show= for the kinds, site= or lat=&lon= for a place, and visible=1 to drop what cannot be seen from it. Subscribe to the address rather than importing it once and it keeps itself current. Every entry is an instant in UTC rather than an all-day block, because an all-day block on the wrong side of a date line is how somebody ends up outside on the wrong night.

[

VARIABLE STARS

] PHASE AND NEXT EVENT

Nearly six thousand stars here carry a variability classification, and for seventeen hundred of them the catalog publishes both a period and the epoch of an observed maximum or minimum. Those two together are an ephemeris: count the whole cycles between them and now, and the remainder says where in its cycle the star is and when the next event falls. Every object record for such a star carries a variability block with the elements; add epoch and it also carries the phase at that instant.

$ curl "https://spacecatalog.org/api/v1/objects/algol?epoch=now"

The instant is the caller’s rather than the server’s because a phase belongs to a moment: computed at whatever time a response happened to be generated, it would go quietly stale in a cache, and for a binary with a six-hour period it would do so within one. phase.value is 0 at the event epoch_marks names — minimum light for eclipsing and ellipsoidal variables and for the RV Tauri and RS Canum Venaticorum types, maximum for every other class — and phase.next lists the coming events as instants. Both are on the heliocentric scale the epochs are published on, so the correction for where the Earth stands in its orbit is in them: it reaches 8.3 minutes, which for a six-hour binary is a fiftieth of a cycle.

A phase is withheld rather than guessed. A star whose class has no ephemeris — a dwarf nova, an irregular or a semiregular variable, where the published period is a mean cycle time the star has no obligation to keep — gets its elements and an unavailable sentence saying so, as does one with a period and no epoch. So does one whose elements have simply worn out: the last digit of a period compounds once per cycle, and after a hundred thousand cycles a period good to a hundredth of a day is not good to anything. phase.uncertainty is that accumulation, and past a quarter of a cycle the answer is the reason instead of the number.

[

ERRORS

] ONE SHAPE

Failures come back as JSON with the same shape, and the status code carries the meaning. A parameter that is out of range says so, by name.

{
  "error": {
    "code": "bad_request",
    "message": "`limit` must be between 1 and 200, got 5000."
  }
}

offset stops at 100,000, and that one names the way out in the message itself: past a hundred thousand rows you are copying the catalog rather than querying it, and the bulk export is one file instead of a thousand requests.

[

RATE LIMIT

] 60/MIN

60 requests a minute per client, with a burst of twice that. Every response carries x-ratelimit-remaining; a 429 carries retry-after in seconds. If you need more than this, take the bulk export rather than polling — the sky is not in a hurry.

[

RELEASE

] ON EVERY RESPONSE

Every response carries a release block naming the frozen, versioned state of the catalog the rows came from — an id, a date and a DOI once one is minted. The site is corrected continuously and a release is not, so quote the release beside any number you take from here. What is in each release, with the licence and retrieval date of every upstream it was built from, and one download that is the whole thing.

A field whose meaning depends on how it was derived — distance_method, radius_kind, stellar_parameters_method, properties.uncertainties — is explained on the methods page: which rungs of the distance ladder are in the catalog, the cosmology a redshift distance is computed under, and what was taken from each upstream and done to it.

Where a value was somebody’s published reading rather than a bulk column — a spectral type, a galaxy’s morphology, the system distance an exoplanet inherits — the provenance string quotes that paper’s ADS bibcode verbatim, as in SIMBAD (2003AJ....126.2048G, quality C). Nineteen characters, resolvable at ADS, so a caller can reach the measurement without asking us for it.

[

LICENSE

] 16 UPSTREAMS
[

NOT AN ENDPOINT, BUT USEFUL

] BULK STAR DATA

The sky map downloads its stars as one packed binary blob from /sky-data/stars — about 120,000 positions in under a megabyte, sorted brightest first, which is what lets an instrument with a limiting magnitude read only the head of the array. It is a fixed binary layout rather than an API, and it is far better at drawing a star field than 120,000 JSON records would be.

A 16-byte little-endian header — uint32 magic 0x53434b59, uint32 version 3, uint32 star count — then five parallel arrays of that count, in this order: uint16 right ascension (a full turn, 20″ per step), int16 declination (±90°, 10″ per step), int16 magnitude in hundredths, uint8 effective temperature packed logarithmically from 1,000 K to 40,000 K across 255 steps, and uint8 refinement — its high nibble subdividing the right-ascension step sixteen ways and its low nibble the declination step, which brings both to sub-pixel at the deepest zoom the map offers. Eight bytes a star.

Positions in this blob are ICRS at epoch J2016.0, not J2000 — they are carried forward by each star’s proper motion so that they agree with the survey sources the map draws over them. The catalogue and the JSON API stay J2000. The version number is in the header and in the URL, so a stale copy fails loudly rather than drawing a sky sixteen years out.