Research Scan
Type to search documentation.

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.

Terminal window
research-scan doctor # a four-line summary and a verdict
research-scan doctor --verbose # every check, with timings and paths
research-scan doctor --json # machine-readable; this is the CI/agent interface

The 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 store
Ready.

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.13
ok ! config path ~/.config/research-scan
ok ! cache path ~/.cache/research-scan
ok OPENALEX_API_KEY **** from user-config
ok OPENALEX_MAILTO **** from user-config
ok S2_API_KEY **** from user-config
ok NCBI_API_KEY **** from user-config
ok ! 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.

Terminal window
research-scan doctor --json
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