scholar-sidekick
Resolve, verify, format and audit scholarly citations from Python.
A thin client for the Scholar Sidekick REST API — citation formatting in 10,000+ CSL styles, bibliography export, retraction and open-access checks, and verification that a citation is real rather than fabricated.
pip install scholar-sidekick
Quick start
from scholar_sidekick import ScholarSidekick
client = ScholarSidekick()
# Format an identifier
print(client.format(text="10.1038/nphys1170", style="vancouver").text)
# Export a bibliography
print(client.export(text="10.1038/nphys1170", format="bibtex"))
Verifying citations
The dominant AI-fabrication pattern is a real, resolvable DOI paired with an invented title.
Such citations resolve cleanly under doi.org, so "follow the link and see if it loads" does not
catch them. Comparing the claimed title against the resolved record does.
result = client.verify(
title="Quantum entanglement in biological systems",
doi="10.1038/nphys1170",
)
print(result.verdict) # matched | mismatch | ambiguous | not_found
print(result.confidence) # high | medium | low
A mismatch is returned as a value, never raised as an exception — it is the expected outcome for
a fabricated citation, and the reason this method exists.
Interpreting verdicts
| verdict | meaning |
|---|---|
matched |
the claimed citation agrees with the resolved record |
mismatch |
the identifier resolves to a different work — likely fabrication |
ambiguous |
a discrepancy that needs human eyes, not an accusation |
not_found |
no record found in the registries searched |
not_found is not proof of fabrication. Standards documents, software repositories, model
cards and institutional reports are frequently real but absent from the scholarly registries the
API searches. Treat it as "could not confirm", not "invented".
Auditing a whole bibliography
The API audits up to 25 references per call and rate-limits those calls. audit_bibliography()
handles both for you: it chunks, paces itself from the server's rate-limit headers, and sends
chunks serially — the server already fans out internally, so parallelising here would compete
with it for the same upstream budget.
report = client.audit_bibliography(
references, progress=lambda done, total: print(f"{done}/{total}")
)
print(report.summary) # verdict counts across every chunk
print(len(report.needs_review)) # entries a human should look at
for entry in report.needs_review:
print(entry.input_index, entry.verdict, entry.title)
entry.input_index is the 0-based position in the list you passed in, so it always points at the
right reference regardless of how the work was chunked.
A chunk that fails is recorded rather than raised, so one transient upstream error does not discard several minutes of completed work:
if not report.complete:
for failure in report.errors:
print(f"entries {failure.start_index}-{failure.end_index} failed: {failure.error}")
With pandas installed (pip install 'scholar-sidekick[pandas]'):
df = report.to_dataframe()
df[df.verdict != "matched"]
There is a runnable end-to-end example in
examples/systematic_review_audit.ipynb.
Authentication
Every endpoint works anonymously — no key, no signup. An API key raises the rate limit (about fivefold on the free tier) but is never required.
client = ScholarSidekick(api_key="ssk_…")
Or set SCHOLAR_SIDEKICK_API_KEY in the environment. Precedence is the explicit argument, then the
environment variable, then anonymous. Free keys are issued at
scholar-sidekick.com/account.
Async
Every method has an async equivalent:
from scholar_sidekick import AsyncScholarSidekick
async with AsyncScholarSidekick() as client:
result = await client.verify(title="…", doi="10.1038/nphys1170")
Note that audit_bibliography() still issues its chunks serially in the async client — the
constraint is the server's upstream budget, not this client's concurrency.
Errors
Exceptions are selected by the API's stable error code, not by HTTP status (the contract
guarantees the code; statuses may change).
from scholar_sidekick import APIError, RateLimitError, UpstreamError
try:
client.format(text="10.1038/nphys1170")
except RateLimitError as exc:
print("retry after", exc.retry_after)
except UpstreamError as exc:
print("a data source failed:", exc.code)
except APIError as exc:
print(exc.code, exc.message, exc.request_id)
| exception | when |
|---|---|
ValidationError |
the request was malformed or invalid |
AuthError |
authentication or entitlement was refused |
RateLimitError |
rate limit exceeded; carries .retry_after |
UpstreamError |
a scholarly data source failed after the API's own retries |
TransportError |
connection failure or timeout; no HTTP response |
Every APIError carries .request_id — include it in bug reports.
API surface
| method | purpose |
|---|---|
format() |
resolve identifiers and format them |
format_items() |
format already-resolved CSL-JSON items |
export() |
export to RIS, BibTeX, CSV, EndNote XML, … |
verify() |
check one citation against the resolved record |
audit() |
audit up to 25 references in one call |
audit_bibliography() |
audit any number, chunked and paced |
health() |
service liveness |
format() returns whole-batch text/html strings covering every input, newline-joined — the API
has no per-item formatted field, and this client does not invent one by splitting that string.
format_items() names the same concept output. The difference is preserved deliberately rather
than papered over.
Related
- Scholar Sidekick API docs
scholar-sidekick-cli— the same API from the terminalscholar-sidekick-mcp— MCP server for AI assistants
Development
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,pandas]"
pytest --cov
License
MIT
Release files for scholar-sidekick 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| scholar_sidekick-0.1.0.tar.gz | 44.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| scholar_sidekick-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 68.9 kB
Release files / scholar_sidekick-0.1.0.tar.gz
| Download URL | scholar_sidekick-0.1.0.tar.gz |
|---|---|
| Size | 44.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f205942c7ad647bf859b1cd1121a5e6fd0d40e46ad0b14baae5e7936c8fd811f
|
|
BLAKE2b-256 checksum How to use checksums |
31899cd28ad25556c75715a84fdbf0666b928ed449fa1a0e6783b3a056380613
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.
Transparency logRelease files / scholar_sidekick-0.1.0-py3-none-any.whl
| Download URL | scholar_sidekick-0.1.0-py3-none-any.whl |
|---|---|
| Size | 24.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
58f6d552d934faabb790e2f504288d8d6cdf23ca57b057d2cfca447eecf58f98
|
|
BLAKE2b-256 checksum How to use checksums |
458dadb1bd5ac733aa4b74bd1ba05a833de29823274fe9661bc13533010b118b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.
Transparency log