Checking your setup
doctor invokes every source live with the cache bypassed, and reports readiness in three output modes that share one exit code.
doctor does not list what it thinks is configured. It invokes every source live
with the cache bypassed, and reports what actually answered.
research-scan doctor # a four-line summary and a verdictresearch-scan doctor --verbose # every check, with timings and pathsresearch-scan doctor --json # machine-readable; this is the CI/agent interfaceThe checks and the exit code are identical in all three modes. Only the presentation differs.
The summary
Research Scan 0.6.1✓ configuration✓ OpenAlex ✓ Semantic Scholar ✓ Crossref ✓ arXiv ○ PubMed PubMed: endpoint reachable; source routed for biomed but not built✓ writable run storeReady.Progress lines go to stderr; only this block is stdout. --quiet silences the
stderr log.
One row to read carefully. ○ marks a source that is routed but has no retrieval
adapter: the PubMed check probes that the E-utilities endpoint answers, and it does,
but the PubMed source is not built yet.
Those are two different questions, and from 0.6.1 the JSON answers them separately —
providers reports what each probe found, and sources_not_built lists what cannot
retrieve regardless of whether its endpoint was reachable.
Exit 0 means go. Exit 3 means a mandatory check failed — read the check name and fix that, rather than working around it.
The full check list
--verbose gives every check with its timing, plus the paths in use.
research-scan doctor — v0.6.1, python 3.13.13
ok python 3.13.13ok ! config path ~/.config/research-scanok ! cache path ~/.cache/research-scanok OPENALEX_API_KEY **** from user-configok OPENALEX_MAILTO **** from user-configok S2_API_KEY **** from user-configok NCBI_API_KEY **** from user-configok ! openalex search 4238497 hits, is_retracted readable, cost $0.001 [1174 ms]ok openalex psyarxiv-doi 10.31234/osf.io/mky9j indexed as preprint [683 ms]ok ! s2 search 1 hit(s) [2088 ms]ok ! s2 references 1 reference(s) — graph expansion available [675 ms]ok crossref lookup 10.1038/s41586-021-03819-2 resolves [368 ms]ok arxiv query parsed a candidate for 'electron' (arXiv:2608.20339) [81 ms]ok pubmed esearch esearch returned pmid 42625563 [529 ms]○ PubMed endpoint reachable; source routed for biomed but not built
paths: config_env: ~/.config/research-scan/.env local_env: ~/.env cache_db: ~/.cache/research-scan/http.sqlite
ready(! marks a check that can exit 3)Credential rows show the last four characters of the real value and where it came from; the values above are masked. Hit counts, arXiv ids, PubMed ids and timings are whatever the live APIs returned at that moment and will differ on every run.
--sources openalex,s2 narrows what is checked.
Reading it from a script or an agent
doctor --json is the interface, and its keys are stable.
research-scan doctor --json{ "version": "0.6.1", "ready": true, "providers": { "openalex": "ok", "s2": "ok", "crossref": "ok", "arxiv": "ok", "pubmed": "ok" }, "sources_not_built": ["pubmed"], "config": "ok", "run_store": "ok", "paths": { "config_env": "~/.config/research-scan/.env", "local_env": "~/.env", "cache_db": "~/.cache/research-scan/http.sqlite" }, "checks": [ { "name": "openalex search", "status": "OK", "detail": "4238497 hits, is_retracted readable, cost $0.001", "mandatory": true, "duration_ms": 1205.4 } ]}Abridged: the real output carries every check in checks, plus a keys object
with each credential’s presence, masked value and origin.
From 0.5.2 this is a stable interface: keys are added, never removed or
repurposed. sources_not_built is the 0.6.1 addition, and it is additive in that
exact sense — providers keeps every key, value and type it had, pubmed included. Read ready for the verdict and version for the tool version. The
ok and tool_version keys emitted by 0.5.0 and 0.5.1 were undocumented
duplicates of those two and are gone.
ready is the boolean. providers maps each checked source to ok / warn /
fail / skip — skip means the probe never ran, which is never the same as
passing. checks carries every individual result.
When a check fails
A missing OPENALEX_API_KEY is a FAIL, and the OpenAlex probes are then
reported as SKIP rather than as passing, because they were never run.
A missing S2_API_KEY is only a WARN. Semantic Scholar search and
citation-graph expansion still run, throttled to roughly a third of the rate.
A missing NCBI_API_KEY is only a WARN, and it costs a scan nothing. The
key raises the rate limit on NCBI E-utilities requests, and the only one the
package makes is the pubmed esearch probe above — PubMed retrieval is not
built, so no scan is throttled by its absence.
arxiv HTTP 429 is a WARN, not a failure. arXiv rate-limits aggressively —
the client already holds to one request every three seconds — and returns 429
under load. A cs scan falls back to OpenAlex and Semantic Scholar, which carry
arXiv preprints anyway: you lose same-day listings and one origin signal, not
coverage. Re-run later if recency matters.
Semantic Scholar answers 429 or 503 when its queue is deep. The HTTP client
retries and then returns the last response rather than raising — a bad status is
data, not an exception. Graph expansion falls back to OpenAlex’s bare-id reference
list, which works but cannot be ranked, so expansion recall drops. It is recorded
per call in expansion.log.jsonl.
One side effect worth knowing
doctor is very nearly read-only, but not entirely: it creates
~/.config/research-scan and ~/.cache/research-scan if they do not exist, and
writes then immediately deletes a probe file in each to prove the run store is
writable. That is the writable run store check.
Last updated Aug 28, 2026