A Claude Code skill and standalone CLI for searching NASA ADS, fetching records, and exporting verbatim bibtex into a .bib file.
Writing astronomy/astrophysics papers and proposals means citing a lot of ADS-indexed work. This skill wraps the ADS API so you can, from Claude Code or a shell:
- Search by author / year / title / text / journal / ORCID / grant / collection
- Pick from a numbered result list
- Pull the verbatim bibtex entry from ADS (no hand-written or hallucinated entries)
- Append to a
.bibfile with duplicate detection — or print for paste-in - Look up records by arXiv ID or DOI (prefers refereed version when one exists)
- List papers citing or referenced by a given bibcode
ads_cite.py is also a self-contained CLI usable without Claude Code (pip-installable as ads-cite).
Requires Python 3.9+. Standard library only; no extra dependencies.
As a Claude Code skill:
git clone https://github.com/gnarayan/ads-cite.git ~/.claude/skills/ads-citeClaude Code picks up the skill automatically on next session. Verify with /skills or invoke /ads-cite help.
As a standalone CLI (installs ads-cite on your PATH, no Claude Code required):
pip install git+https://github.com/gnarayan/ads-cite.git
# or once on PyPI: pip install ads-cite
ads-cite --helpBoth paths share the same token config (next section). If you install as a skill, also grant the permission described further below.
Get a token from https://ui.adsabs.harvard.edu → Account Settings → API Token. ADS allows 5000 API calls/day per token; a search + bibtex export is 2 calls. The script looks for the token in this order (first match wins):
- macOS Keychain (recommended on Mac):
security add-generic-password -a "$USER" -s "nasa-ads-api-token" -w "<TOKEN>" -U
- Environment variable (recommended on linux):
export ADS_DEV_KEY="<TOKEN>" # or ADS_API_TOKEN
- File (portable fallback):
mkdir -p ~/.ads && echo "<TOKEN>" > ~/.ads/dev_key && chmod 600 ~/.ads/dev_key
Add this line to ~/.claude/settings.local.json under permissions.allow so Claude Code doesn't prompt on every script run:
"Bash(python3 ~/.claude/skills/ads-cite/ads_cite.py:*)"Installing the skill is not enough. Claude defaults to writing bibtex from
memory when asked for a citation, and it will hallucinate journal names,
volumes, page numbers, and author lists. Add a rule to your global
~/.claude/CLAUDE.md that redirects all bibtex requests through ads-cite.
Paste this into your ~/.claude/CLAUDE.md (create the file if it doesn't exist):
## Bibliography rules (STRICT)
- **NEVER hand-write or generate bibtex entries.** Every `.bib` entry must
come verbatim from NASA ADS's export endpoint.
- Always use `.bib` files with `natbib` or `biblatex` — never hardcode
citations in `.tex`.
- **Workflow:** use the `/ads-cite` skill (or `ads-cite` CLI directly) to
search ADS, pick the result, then either:
- `ads-cite append --rekey <BIBFILE> <BIBCODE>` — appends verbatim bibtex
with a memorable `LastName_Subject_Year` citekey and preserves the
original bibcode as a `% ADS bibcode:` comment; skips duplicates
- `ads-cite bibtex --rekey <BIBCODE>` — prints bibtex for paste-in
- With `--rekey`, cite as `\citep{Narayan_ESSENCE_2016}`. Without it, the
citekey is the raw bibcode (`\citep{2016ApJS..224....3N}`). Match whatever
convention the existing `.bib` file already uses. No URL comment needed —
the ADS URL is in the bibtex `adsurl` field.With this in place, when you ask Claude for a citation or to add references
to a draft, it will run ads-cite instead of making up a bibtex entry.
/ads-cite Narayan 2024 white dwarf calibration
/ads-cite ^Coelho 2020 # first-author Coelho, year 2020
/ads-cite kilonova r-process 2017
/ads-cite help # print full usage
Claude parses author/year/text arguments automatically, runs the search, shows a numbered list, asks which one you want, and either appends to a .bib in CWD or prints the bibtex for paste-in.
Raw ADS field syntax works too:
/ads-cite author:"Scolnic" bibstem:ApJ year:2022-2024
/ads-cite bibgroup:DESC first_author:"Malz" keyword:"photo-z"
ads-cite search "author:^Narayan year:2024"
ads-cite show 2016ApJS..224....3N
ads-cite bibtex 2016ApJS..224....3N
ads-cite citations 2016ApJS..224....3N
ads-cite references 2016ApJS..224....3N
ads-cite arxiv 2510.07637
ads-cite doi 10.3847/0067-0049/224/1/3
ads-cite append refs.bib 2016ApJS..224....3N 2025PASP..137b4101S
ads-cite --help| Flag | Applies to | Effect |
|---|---|---|
--json |
all verbs | Emit structured JSON instead of formatted text. Useful for scripts and agent tool calls. |
--rows N |
search, citations, references |
Override the default row cap (10 / 20 / 50). |
--sort "FIELD DIR" |
search, citations, references |
Override sort, e.g. --sort "citation_count desc". |
--rekey |
bibtex, append |
Rewrite citekey as LastName_Subject_Year (e.g., Narayan_ESSENCE_2016). Prepends % ADS bibcode: <X> as a comment, so the original identifier is preserved and dedup still works across both styles. |
--subject WORD |
bibtex, append (with --rekey) |
Explicit subject for the citekey. With multiple bibcodes, the same word applies to all entries (each still gets a unique key via differing LastName / Year). If omitted, auto-derived per entry from the title (prefers uppercase acronyms like ESSENCE, LSST; demotes object/catalog designations like PLCK, NGC, G165). |
--collab-tag TAG |
bibtex, append (with --rekey) |
Citekey lastname for collaboration/institutional first authors (e.g. --collab-tag LSSTDESC), overriding the auto-derived tag. The auto-tag keeps all-caps tokens whole and contributes initials of other words (LSST Dark Energy Science Collaboration → LSSTDESC, The PLAsTiCC team → PLAsTiCC); a stderr WARN always recommends overriding it. |
--rekey-report |
bibtex, append (with --rekey) |
Dry run. Print the proposed bibcode -> citekey [flags] table with low-confidence markers and exit without writing or emitting bibtex. Use it to review a bulk rekey before committing. |
--include-unrefereed |
search, citations, references |
Drop the default doctype:(article OR eprint) filter so conference proceedings, theses, meeting abstracts, and other non-journal content also appear in results. |
ADS bibcodes (2016ApJS..224....3N) are unambiguous but hard to type or remember when you're citing a paper by name. --rekey transforms each entry to a human-friendly citekey while keeping the bibcode safe:
# Auto-derive subject from title
$ ads-cite bibtex --rekey 2016ApJS..224....3N
% ADS bibcode: 2016ApJS..224....3N
@ARTICLE{Narayan_ESSENCE_2016,
...
}
# Explicit subject override
$ ads-cite bibtex --rekey --subject SN2023ixf 2024ApJ...XXX..YYYZ
% ADS bibcode: 2024ApJ...XXX..YYYZ
@ARTICLE{Author_SN2023ixf_2024,
...
}
# Append with rekey — your .tex file uses \citep{Narayan_ESSENCE_2016}
$ ads-cite append --rekey refs.bib 2016ApJS..224....3N
# Preview a bulk rekey before writing anything
$ ads-cite bibtex --rekey --rekey-report 2018ApJ...859..101S 2023ApJS..267...25H
Rekey report (dry run — nothing written):
2018ApJ...859..101S -> Scolnic_Spectroscopically_2018 [weak subject; pass --subject]
2023ApJS..267...25H -> Hlozek_LSST_2023 [surname accent-repaired]Dedup is robust across styles: if you've already appended a bibcode with --rekey, appending the same bibcode later (with or without --rekey) will skip it, because the bibcode lives in the preserved % ADS bibcode: comment.
When a rekey is low-confidence, a WARN line is printed to stderr (the bibtex still goes to stdout, so piping is unaffected). Triggers: an auto-derived subject that fell back to a generic word, a collaboration/institutional first author, a surname whose LaTeX accents were repaired, or a missing author/year. This is what surfaces the rare bad key when rekeying in bulk — pass --subject / --collab-tag to fix the flagged entries, or run --rekey-report first to review the whole batch.
# Lit review: get JSON of top 50 papers citing SELDON, most-cited first
ads-cite citations 2603.04392 --rows 50 --json
# Dedup-append the top result of a search to your proposal's .bib
ads-cite search 'first_author:"Pierel" title:"H0pe"' --json | jq -r '.[0].bibcode' \
| xargs ads-cite append proposal.bib
# What does this paper build on?
ads-cite references 2024PASP..136f4501G --sort "citation_count desc" --rows 20| Field | Example |
|---|---|
author: |
author:"Narayan, G." (use ^Name for first author) |
first_author: |
first_author:"Hawking, S" |
title: |
title:"dark energy" |
abs: |
abs:"gravitational waves" |
year: |
year:2020-2024 or year:2023 |
bibstem: |
bibstem:ApJ |
aff: |
aff:"Illinois" |
orcid_pub: |
orcid_pub:0000-0001-XXXX-XXXX |
keyword: |
keyword:"dark energy" |
bibgroup: |
bibgroup:DESC |
grant: |
grant:"DE-SC0025232" |
arxiv_class: |
arxiv_class:astro-ph.CO |
| free text (quoted) | "GW170817" |
Default filters applied: database:astronomy + doctype:(article OR eprint) — so AAS abstracts, PhD theses, and conference proceedings are excluded; refereed journals and arXiv preprints both show. Pass --include-unrefereed on search / citations / references to drop the doctype filter and see all content types.
"No ADS record found" for a bibcode / arXiv ID / DOI you know exists. Check the input for silent mangling:
- arXiv:
v2suffixes, URL wrappers (https://arxiv.org/abs/…), PDF URLs (https://arxiv.org/pdf/…,.pdfstripped), and old-style category IDs (quant-ph/0012345) are all accepted. - DOI:
https://doi.org/…,dx.doi.org/…, anddoi:prefixes are stripped automatically. - Bibcode: must be 19 chars, 4-digit year + 15 compact chars, no spaces.
ads-citevalidates format and prints a clear error if it's malformed.
401 from ADS — the token is wrong or was copy-pasted with surrounding quotes or newlines. Regenerate at https://ui.adsabs.harvard.edu → Account Settings → API Token and re-store it.
Keychain keeps prompting — click "Always Allow" the first time. If you already dismissed the dialog, delete the entry (security delete-generic-password -a "$USER" -s "nasa-ads-api-token") and re-add it.
Rekey'd citekeys look wrong (e.g., a generic subject like Paper) — low-confidence rekeys now print a WARN to stderr naming the problem; pass --subject WORD (or --collab-tag TAG for collaboration authors) to fix them, or run --rekey-report to review a batch before writing. Accented surnames are repaired to their base letters (Müller → Muller, Hlo{\v{z}ek} → Hlozek, Sánchez → Sanchez) rather than truncated; non-ASCII is folded via NFKD.
Duplicate citekeys from --rekey (two papers by the same author, same year, same auto-derived subject) — you'll get two entries with identical citekeys. LaTeX will warn. Run --rekey-report to spot the collision in advance, then override with a distinguishing --subject for one of them.
Appending to a path whose parent directory doesn't exist — ads-cite creates the parent directory automatically. Good for starting a new proposal.
Mixing --rekey and non-rekey styles in one .bib — works; dedup scans both the citekey and the % ADS bibcode: comment. As a third safety net, append also compares the doi = {…} field of each fetched entry against DOIs already in the file, which catches hand-written entries that share a paper with no % ADS bibcode: marker. But LaTeX users will find it jarring to see both styles; pick one per file.
.bib file with a BOM or DOS line endings — BOM is stripped on read. DOS endings don't affect the dedup regex.
Preprint returned when you wanted the refereed version — arxiv subcommand already prefers articles over eprints. For direct search, if you see an arXiv bibcode, check ADS for a linked published version.
Matching existing .bib convention — if the file already has entries keyed by raw bibcode, drop --rekey to keep the file consistent.
MIT — see LICENSE.
- NASA ADS for the API and verbatim bibtex export
- adsabs-dev-api for the API docs