semantic-scholar-api — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited semantic-scholar-api (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
| API | Strength | Rate limit (no key) |
|---|---|---|
Semantic Scholar api.semanticscholar.org/graph/v1/ | Rich citation graph, influence scoring, AI-extracted tldr | 1 req/sec |
OpenAlex api.openalex.org/ | Larger coverage (250M+ works), institution + funding data | 10 req/sec with email in User-Agent |
Use S2 for depth (citations, references, influence), OpenAlex for breadth (coverage, metadata richness).
GET /graph/v1/paper/{id}
GET /graph/v1/paper/{id}/citations # who cites this paper
GET /graph/v1/paper/{id}/references # what this paper cites
GET /graph/v1/paper/search?query=...
GET /graph/v1/author/{id}/papersarXiv:2604.13012 or ARXIV:2604.1301210.1038/s41586-023-06792-0PMID:34567890CorpusId:12345678649def34f8be52c8b66281af98ae884c09aef38bimport requests, time
BASE = "https://api.semanticscholar.org/graph/v1"
HEADERS = {"User-Agent": "research-tool/1.0 ([email protected])"}
def paper(pid, fields="title,abstract,tldr,year,citationCount,authors"):
r = requests.get(f"{BASE}/paper/{pid}", params={"fields": fields}, headers=HEADERS, timeout=15)
r.raise_for_status()
return r.json()
def citations(pid, limit=20, fields="title,year,citationCount,authors"):
r = requests.get(
f"{BASE}/paper/{pid}/citations",
params={"fields": fields, "limit": limit},
headers=HEADERS, timeout=15,
)
r.raise_for_status()
return [c["citingPaper"] for c in r.json().get("data", [])]
def references(pid, limit=20, fields="title,year,citationCount"):
r = requests.get(
f"{BASE}/paper/{pid}/references",
params={"fields": fields, "limit": limit},
headers=HEADERS, timeout=15,
)
r.raise_for_status()
return [c["citedPaper"] for c in r.json().get("data", [])]
# Example: walk 1 hop out from an arxiv paper
seed = paper("arXiv:2604.13032")
print(seed["title"], "→", seed["tldr"])
time.sleep(1) # rate limit
for citer in citations("arXiv:2604.13032", limit=5):
print(" cited by:", citer["title"])| Field | Purpose |
|---|---|
tldr.text | S2's AI-generated one-sentence summary — free, often excellent |
citationCount | Raw citations |
influentialCitationCount | Citations where this paper "significantly shaped" the citer (better signal than raw) |
openAccessPdf.url | Direct PDF if OA |
embedding | 768-dim SPECTER2 embedding (for semantic search) |
authors[].hIndex | Author stature |
BASE_OA = "https://api.openalex.org"
HEADERS_OA = {"User-Agent": "research-tool/1.0 (mailto:[email protected])"} # include mailto for polite-pool
def openalex_paper(doi_or_id):
# DOI: https://api.openalex.org/works/doi:10.1038/...
# OpenAlex ID: W2741809807
# arxiv: use search since no direct arxiv lookup
r = requests.get(f"{BASE_OA}/works/{doi_or_id}", headers=HEADERS_OA, timeout=15)
r.raise_for_status()
return r.json()
def openalex_search_by_concept(concept_id, filters=None, per_page=25):
# concept IDs like "C121332964" (physics)
params = {"filter": f"concepts.id:{concept_id}", "per_page": per_page}
if filters: params["filter"] += "," + filters
r = requests.get(f"{BASE_OA}/works", params=params, headers=HEADERS_OA, timeout=15)
return r.json().get("results", [])oa_status: gold/bronze/green/closed per papercited_by_api_url — no limitrefs = references("arXiv:2604.13032", limit=100)
# Sort by influence
refs.sort(key=lambda p: p.get("citationCount", 0), reverse=True)
top_ancestors = refs[:10]citers = citations("arXiv:2604.13032", limit=100)
recent = [p for p in citers if (p.get("year") or 0) >= 2024]
recent.sort(key=lambda p: p.get("citationCount", 0), reverse=True)# S2 tracks authors with internal IDs; OpenAlex uses ORCID when available
r = requests.get(f"{BASE}/author/search", params={"query": "Smith J", "limit": 10})
candidates = r.json().get("data", [])
# Filter by affiliation or paper countp = paper("arXiv:2604.13012", fields="title,abstract,tldr")
# p["abstract"] is clean (no LaTeX in most cases)
# p["tldr"]["text"] is the AI 1-linermailto param): 10 req/secAlways retry with exponential backoff on 429/500; cache results locally; persist paper JSON for 30+ days since metadata rarely changes.
| Task | Best tool |
|---|---|
| "Most recent papers in a category" | arXiv |
| "Papers matching complex keyword query" | arXiv (search_query supports AND/OR/NOT) |
| "What cites this paper" | S2 or OpenAlex |
| "What does this paper cite" | S2 (better metadata than arXiv's embedded references) |
| "Get abstract + DOI + OA link" | S2 or OpenAlex |
| "Author's full publication record" | OpenAlex |
| "Which institutions research topic X" | OpenAlex |
| "Influential papers in topic X" | S2 influentialCitationCount |
$ scan: if '$' in abstract: # strip or MathJax render.selfCitationCount (via contexts.isSelfCited)._Last reviewed: 2026-05-14 — automated polish pass per issue #86._
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.