Three HATS catalogs (HEALPix-partitioned Parquet), listed in catalogs/latest.json.
varchive_scalars
One row per star (10,304,679): ids (varchive_id, asas_sn_id, vsx_oid, name), VSX fields, coverage, features, periods, classification, crossmatches and reddening. No light curves.
curves_static
Frozen light curves: fixed data releases (OGLE, ASAS-3, MASCARA) and ASAS-SN's archival V band.
curves_live
Light curves a survey can still add to: ASAS-SN g, Gaia and ZTF. Same layout.
Collections are partitioned differently; join on varchive_id, not partition number. The pointer's shared path holds one link table per survey, links/<survey>.parquet: how each survey source was matched to a star (method, grade, separation, candidates, radius, magnitude check).
Where each survey's light curve is
Each row of curves_static and curves_live is one star: varchive_id, ra, dec and one nested column per survey, null when the star has no light curve from it. Every nested column has band, bjd_tdb, mag, mag_err, detected; the rest are the survey's own.
curves_static
column
survey
bands
its own columns
lc_asassn
ASAS-SN
V
jd, limit, camera, quality, fwhm, image_id
lc_ogle
OGLE
I, V · series ii, iii, iv
series, hjd
lc_asas3
ASAS-3
V
hjd, frame, dataset_block, grade, aperture
lc_mascara
MASCARA
V
hjd, mag_delta
curves_live
column
survey
bands
its own columns
lc_asassn
ASAS-SN
g
jd, limit, camera, quality, fwhm, image_id
lc_gaia
Gaia
G, BP, RP
—
lc_ztf
ZTF
g, r, i
hmjd, catflags, fieldid, rcid
ASAS-SN appears in both collections under the same column, lc_asassn: V epochs in one, g in the other. detected is false for a non-detection; only ASAS-SN and Gaia report them, so filter on it before using magnitudes. Use one band (and one OGLE series) at a time.
02
Read in bulk with LSDB
LSDB
lsdb reads only the partitions and columns you request, when you call .compute().
Pythonopen the release, filter, read a cone
import json, lsdbfrom upath import UPathroot = UPath("https://data.varchive.app")ptr = json.loads((root / "catalogs/latest.json").read_text())path = lambda c: root / ptr["collections"][c]["path"]# the scalars: one row per starstars = lsdb.read_hats(path("varchive_scalars"), columns=["varchive_id", "name", "ra", "dec", "vsx_broad_class", "adopted_period", "adopted_survey", "is_periodic"])rrl = stars.query("vsx_broad_class == 'RR_LYRAE' and is_periodic").compute()# one survey's light curves around a position (radius in arcsec)live = lsdb.read_hats(path("curves_live"), columns=["varchive_id", "lc_ztf"])field = live.cone_search(ra=6.008, dec=38.577, radius_arcsec=60).compute()field = field[field["lc_ztf"].notna()] # the stars ZTF observedlc = field["lc_ztf"].iloc[0] # band, bjd_tdb, mag, mag_err, detected, hmjd, ...r = lc[lc["band"] == "r"] # one band at a time
Select stars on any scalars column, then read any survey's light curves for just those stars. The second step reads only the partitions the selected stars sit in and matches on varchive_id.
Filtervarchive_scalars → the stars you want
Fetchtheir partitions of curves_static / curves_live → one row per epoch
PythonRRab stars around M3, then their ZTF and ASAS-SN light curves
import jsonimport lsdbimport pandas as pdfrom hats.pixel_math import HealpixPixelfrom upath import UPathroot = UPath("https://data.varchive.app")ptr = json.loads((root / "catalogs/latest.json").read_text())path = lambda c: root / ptr["collections"][c]["path"]# the collection(s) holding each survey's light curves (table above); the column is lc_<survey>COLLECTIONS = { "asassn": ["curves_static", "curves_live"], # V is static, g is live "gaia": ["curves_live"], "ztf": ["curves_live"], "ogle": ["curves_static"], "asas3": ["curves_static"], "mascara": ["curves_static"],}# 1. filter the scalars: here, periodic RRab stars within 2 degrees of M3sel = lsdb.read_hats( path("varchive_scalars"), search_filter=lsdb.ConeSearch(ra=205.548, dec=28.377, radius_arcsec=7200), columns=["varchive_id", "name", "ra", "dec", "vsx_type", "adopted_period", "is_periodic"],).query("vsx_type == 'RRAB' and is_periodic").compute()# 2. one survey's epochs for those stars, one row per epochdef light_curves(sel, survey): col = f"lc_{survey}" # sel's index is _healpix_29; >> 40 gives each star's order-9 HEALPix cell cells = {HealpixPixel(9, int(h) >> 40) for h in sel.index} tables = [] for collection in COLLECTIONS[survey]: lc = lsdb.read_hats(path(collection), columns=["varchive_id", col], search_filter=lsdb.PixelSearch(list(cells))).compute() lc = lc[lc["varchive_id"].isin(sel["varchive_id"]) & lc[col].notna()] tables.append(lc.set_index("varchive_id")[col].nest.to_flat().reset_index()) return pd.concat(tables, ignore_index=True)ztf = light_curves(sel, "ztf") # varchive_id, band, bjd_tdb, mag, mag_err, detected, hmjd, ...asassn = light_curves(sel, "asassn") # V and g together, each row naming its bandztf.to_parquet("m3_rrab_ztf.parquet")# analyze one star in one band at a timestar = ztf[(ztf["varchive_id"] == sel["varchive_id"].iloc[0]) & (ztf["band"] == "r")]
As written, this selects 105 stars and returns ZTF light curves for 60 of them (46,638 epochs in g, r and i) in about 30 s. Swap in any survey ID; a survey with no light curves in the region returns an empty table. Without the cone, step 1 filters all 10,304,679 stars in about two minutes for a few columns (LSDB warns about the catalog's size, but reads only the columns you name). Step 2's cost follows the number of partitions the stars fall in, not the number of stars, so a selection spread over the whole sky reads most of that survey's column.
04
One star in Python
HTTP
For a few stars, use the API. Keys: varchive_id, ASAS-SN ID or VSX name.
PythonYY Crv: scalars and light curves
import io, requestsimport pandas as pdBASE = "https://api.varchive.app"row = requests.get(f"{BASE}/star/YY Crv", params={"fields": "core,periods"}).json()["star"]row["adopted_period"], row["period_source"], row["adopted_survey"], row["is_periodic"]# one survey's epochs straight into a DataFrame, via the CSV exportcsv = requests.get(f"{BASE}/star/YY Crv/lightcurve.csv", params={"survey": "asassn"}).textlc = pd.read_csv(io.StringIO(csv), comment="#")v = lc[lc["band"] == "V"]# all surveys in one table, with a survey columnall_lc = pd.read_csv(io.StringIO( requests.get(f"{BASE}/star/YY Crv/lightcurve.csv", params={"survey": "all"}).text), comment="#")all_lc.groupby(["survey", "band"]).size()
05
The endpoints
HTTP
https://api.varchive.app, no key. {key} is a varchive_id, an ASAS-SN ID or a VSX name. Responses name the collection versions they read. Each example opens live.
Stored GLS peaks for one survey band: unit is asassn:g, ogle:iv:I, …, or gaia:mb for Gaia's joint fit. The full periodogram comes with the star's primary light curve, the default when unit is omitted. kind=bls gives the BLS peaks.
Filter the whole archive by class, period, magnitude, survey or position (below).
Shellfrom a terminal
# the star's core scalarscurl "https://api.varchive.app/star/YY%20Crv?fields=core"# every survey's epochs in one CSVcurl "https://api.varchive.app/star/YY%20Crv/lightcurve.csv?survey=all" -o yy_crv.csv
Light curves include bjd_tdb, band, mag, mag_err, detected and native columns.
Uncached requests are limited per address, and heavier searches count more. A refused request gets 429 with Retry-After. For bulk work, read the collections with LSDB, which has no limit.
06
Search & cone search
HTTP
Filter by class, period, magnitude, survey and cross-survey agreement, or by position. Returns active VSX stars by default. The search page shows the API call for each query.
Shell/search
# short-period eclipsing binaries brighter than 14 magcurl "https://api.varchive.app/search?broad_class=ECLIPSING&period_max=1&mag_max=14"# stars with an OGLE light curve, periods confirmed by two surveyscurl "https://api.varchive.app/search?survey=ogle&corroborated=true"# stars with light curves from at least three surveyscurl "https://api.varchive.app/search?n_surveys_min=3"# cone search: ra, dec in degrees, radius in arcsec (at most 3600)curl "https://api.varchive.app/search?ra=324.022&dec=-79.658&radius_arcsec=1800"
mag_min/mag_max filter mean_mag (ASAS-SN g, else V, else Gaia G). Page with limit (at most 1,000) and offset (at most 10,000). When the match count is only a lower bound (past 10,000 matches, or a sparse filter or wide cone that reached its scan limit), the response sets total_is_lower_bound.
07
Time and magnitude systems
Reference
◷
One time axis
Every epoch carries bjd_tdb (float64), converted from each survey's own clock. The native column stays beside it: jd for ASAS-SN, hmjd for ZTF, hjd for OGLE, ASAS-3 and MASCARA.
⚑
Never mix bands
Magnitudes stay in each survey's own band and system. Do not concatenate magnitudes across bands or surveys; combine them only with models that fit a separate offset per series.
Adopted periods and epochs are the archive's own; VSX values are kept for comparison.
08
Cite what you use
Reference
Cite the archive and each survey whose data you use. Star pages and exports list the references. AAVSO VSX and Gaia apply to every star. The archive's own data are under CC BY 4.0; each survey's data stay under the license below, and so do the catalogs and dust maps in the second table, whose values ride beside the light curves.