pub struct OpenAlexClient { /* private fields */ }Expand description
Async client for the OpenAlex REST API.
Provides 30 methods covering all OpenAlex endpoints: 10 list, 10 get, 8 autocomplete, and 2 semantic search.
§Creating a client
use papers_openalex::OpenAlexClient;
// Reads API key from OPENALEX_KEY env var (optional but recommended)
let client = OpenAlexClient::new();
// Or pass an explicit API key
let client = OpenAlexClient::with_api_key("your-key-here");§Example: search and paginate
use papers_openalex::{OpenAlexClient, ListParams};
let client = OpenAlexClient::new();
let params = ListParams::builder()
.search("machine learning")
.filter("publication_year:2024,is_oa:true")
.sort("cited_by_count:desc")
.per_page(5)
.build();
let response = client.list_works(¶ms).await?;
for work in &response.results {
println!("{}: {} cites",
work.display_name.as_deref().unwrap_or("?"),
work.cited_by_count.unwrap_or(0));
}§Example: cursor pagination
use papers_openalex::{OpenAlexClient, ListParams};
let client = OpenAlexClient::new();
let mut cursor = Some("*".to_string());
while let Some(c) = cursor {
let params = ListParams {
cursor: Some(c),
per_page: Some(200),
filter: Some("publication_year:2024".into()),
..Default::default()
};
let response = client.list_works(¶ms).await?;
for work in &response.results {
// process each work
}
cursor = response.meta.next_cursor;
}Implementations§
Source§impl OpenAlexClient
impl OpenAlexClient
Sourcepub fn new() -> OpenAlexClient
pub fn new() -> OpenAlexClient
Create a new client, reading the API key from the OPENALEX_KEY
environment variable. The key is optional for most endpoints but
recommended for higher rate limits.
use papers_openalex::OpenAlexClient;
let client = OpenAlexClient::new();Sourcepub fn with_api_key(api_key: impl Into<String>) -> OpenAlexClient
pub fn with_api_key(api_key: impl Into<String>) -> OpenAlexClient
Create a new client with an explicit API key.
use papers_openalex::OpenAlexClient;
let client = OpenAlexClient::with_api_key("your-key-here");Sourcepub fn with_base_url(self, url: impl Into<String>) -> OpenAlexClient
pub fn with_base_url(self, url: impl Into<String>) -> OpenAlexClient
Override the base URL. Useful for testing with a mock server.
use papers_openalex::OpenAlexClient;
let client = OpenAlexClient::new()
.with_base_url("http://localhost:8080");Sourcepub fn with_cache(self, cache: DiskCache) -> OpenAlexClient
pub fn with_cache(self, cache: DiskCache) -> OpenAlexClient
Enable disk caching of successful responses.
use papers_openalex::{OpenAlexClient, DiskCache};
use std::time::Duration;
let cache = DiskCache::default_location(Duration::from_secs(600)).unwrap();
let client = OpenAlexClient::new().with_cache(cache);Sourcepub async fn list_works(
&self,
params: &ListParams,
) -> Result<ListResponse<Work>, OpenAlexError>
pub async fn list_works( &self, params: &ListParams, ) -> Result<ListResponse<Work>, OpenAlexError>
List scholarly works (articles, books, datasets, preprints). 240M+ records. Supports full-text search across titles, abstracts, and full text. Filter by publication year, OA status, type, citations, author, institution, topic, funder, and 130+ other fields.
GET /works
§Example
use papers_openalex::{OpenAlexClient, ListParams};
let client = OpenAlexClient::new();
let params = ListParams::builder()
.filter("publication_year:2024,is_oa:true")
.sort("cited_by_count:desc")
.per_page(5)
.build();
let response = client.list_works(¶ms).await?;
// response.meta.count => total matching works
// response.results => Vec<Work>List disambiguated author profiles. 110M+ records. Each author has a unified identity across name variants, with linked ORCID, institutional affiliations, publication history, and citation metrics.
GET /authors
§Example
use papers_openalex::{OpenAlexClient, ListParams};
let client = OpenAlexClient::new();
let params = ListParams::builder()
.search("einstein")
.per_page(3)
.build();
let response = client.list_authors(¶ms).await?;
for author in &response.results {
println!("{}: {} works",
author.display_name.as_deref().unwrap_or("?"),
author.works_count.unwrap_or(0));
}Sourcepub async fn list_sources(
&self,
params: &ListParams,
) -> Result<ListResponse<Source>, OpenAlexError>
pub async fn list_sources( &self, params: &ListParams, ) -> Result<ListResponse<Source>, OpenAlexError>
List publishing venues: journals, repositories, conferences, ebook platforms, and book series. Includes ISSN identifiers, OA status, APC pricing, host organization, and impact metrics.
GET /sources
§Example
use papers_openalex::{OpenAlexClient, ListParams};
let client = OpenAlexClient::new();
let params = ListParams::builder()
.filter("is_oa:true,type:journal")
.sort("cited_by_count:desc")
.per_page(5)
.build();
let response = client.list_sources(¶ms).await?;Sourcepub async fn list_institutions(
&self,
params: &ListParams,
) -> Result<ListResponse<Institution>, OpenAlexError>
pub async fn list_institutions( &self, params: &ListParams, ) -> Result<ListResponse<Institution>, OpenAlexError>
List research organizations: universities, hospitals, companies, government agencies. Linked to ROR identifiers. Includes geographic location, parent/child relationships, and affiliated repositories.
GET /institutions
§Example
use papers_openalex::{OpenAlexClient, ListParams};
let client = OpenAlexClient::new();
let params = ListParams::builder()
.filter("country_code:US,type:education")
.sort("cited_by_count:desc")
.per_page(10)
.build();
let response = client.list_institutions(¶ms).await?;Sourcepub async fn list_topics(
&self,
params: &ListParams,
) -> Result<ListResponse<Topic>, OpenAlexError>
pub async fn list_topics( &self, params: &ListParams, ) -> Result<ListResponse<Topic>, OpenAlexError>
List research topics organized in a 3-level hierarchy: domain > field > subfield > topic. AI-generated descriptions and keywords. Each work is assigned up to 3 topics with relevance scores.
GET /topics
§Example
use papers_openalex::{OpenAlexClient, ListParams};
let client = OpenAlexClient::new();
let params = ListParams::builder()
.search("machine learning")
.per_page(5)
.build();
let response = client.list_topics(¶ms).await?;Sourcepub async fn list_publishers(
&self,
params: &ListParams,
) -> Result<ListResponse<Publisher>, OpenAlexError>
pub async fn list_publishers( &self, params: &ListParams, ) -> Result<ListResponse<Publisher>, OpenAlexError>
List publishing organizations (e.g. Elsevier, Springer Nature). Includes
parent/child hierarchy, country of origin, and linked sources. Some
publishers also act as funders or institutions (see roles).
GET /publishers
§Example
use papers_openalex::{OpenAlexClient, ListParams};
let client = OpenAlexClient::new();
let params = ListParams::builder()
.sort("works_count:desc")
.per_page(10)
.build();
let response = client.list_publishers(¶ms).await?;Sourcepub async fn list_domains(
&self,
params: &ListParams,
) -> Result<ListResponse<Domain>, OpenAlexError>
pub async fn list_domains( &self, params: &ListParams, ) -> Result<ListResponse<Domain>, OpenAlexError>
List research domains (broadest level of topic hierarchy). 4 domains total: Life Sciences, Social Sciences, Physical Sciences, Health Sciences. Each domain contains multiple academic fields.
GET /domains
§Example
use papers_openalex::{OpenAlexClient, ListParams};
let client = OpenAlexClient::new();
let response = client.list_domains(&ListParams::default()).await?;
for domain in &response.results {
println!("{}: {} works",
domain.display_name.as_deref().unwrap_or("?"),
domain.works_count.unwrap_or(0));
}Sourcepub async fn list_fields(
&self,
params: &ListParams,
) -> Result<ListResponse<Field>, OpenAlexError>
pub async fn list_fields( &self, params: &ListParams, ) -> Result<ListResponse<Field>, OpenAlexError>
List academic fields (second level of topic hierarchy). 26 fields total (e.g. Computer Science, Medicine, Mathematics). Each field has a parent domain and contains multiple subfields.
GET /fields
§Example
use papers_openalex::{OpenAlexClient, ListParams};
let client = OpenAlexClient::new();
let response = client.list_fields(&ListParams::default()).await?;
for field in &response.results {
println!("{}: {} works",
field.display_name.as_deref().unwrap_or("?"),
field.works_count.unwrap_or(0));
}Sourcepub async fn list_subfields(
&self,
params: &ListParams,
) -> Result<ListResponse<Subfield>, OpenAlexError>
pub async fn list_subfields( &self, params: &ListParams, ) -> Result<ListResponse<Subfield>, OpenAlexError>
List research subfields (third level of topic hierarchy). ~252 subfields total (e.g. Artificial Intelligence, Organic Chemistry). Each subfield has a parent field and domain, and contains multiple topics.
GET /subfields
§Example
use papers_openalex::{OpenAlexClient, ListParams};
let client = OpenAlexClient::new();
let response = client.list_subfields(&ListParams::default()).await?;
for subfield in &response.results {
println!("{}: {} works",
subfield.display_name.as_deref().unwrap_or("?"),
subfield.works_count.unwrap_or(0));
}Sourcepub async fn list_funders(
&self,
params: &ListParams,
) -> Result<ListResponse<Funder>, OpenAlexError>
pub async fn list_funders( &self, params: &ListParams, ) -> Result<ListResponse<Funder>, OpenAlexError>
List research funding organizations (e.g. NIH, NSF, ERC). Linked to Crossref funder registry. Includes grant counts, funded works, and impact metrics.
GET /funders
§Example
use papers_openalex::{OpenAlexClient, ListParams};
let client = OpenAlexClient::new();
let params = ListParams::builder()
.filter("country_code:US")
.sort("works_count:desc")
.per_page(5)
.build();
let response = client.list_funders(¶ms).await?;Sourcepub async fn get_work(
&self,
id: &str,
params: &GetParams,
) -> Result<Work, OpenAlexError>
pub async fn get_work( &self, id: &str, params: &GetParams, ) -> Result<Work, OpenAlexError>
Get a single scholarly work by ID. Returns full metadata including title, authors, abstract (as inverted index), citations, topics, OA status, locations, funding, and bibliographic data.
GET /works/{id}
Accepts: OpenAlex ID (W...), DOI, PMID (pmid:...), PMCID
(pmcid:...), MAG (mag:...).
§Example
use papers_openalex::{OpenAlexClient, GetParams};
let client = OpenAlexClient::new();
// By OpenAlex ID
let work = client.get_work("W2741809807", &GetParams::default()).await?;
// By DOI
let work = client.get_work("https://doi.org/10.7717/peerj.4375", &GetParams::default()).await?;
// With field selection
let params = GetParams::builder().select("id,display_name,cited_by_count").build();
let work = client.get_work("W2741809807", ¶ms).await?;Get a single author profile. Returns disambiguated identity with name variants, institutional affiliations over time, publication/citation counts, h-index, and topic expertise.
GET /authors/{id}
Accepts: OpenAlex ID (A...), ORCID.
§Example
use papers_openalex::{OpenAlexClient, GetParams};
let client = OpenAlexClient::new();
let author = client.get_author("A5023888391", &GetParams::default()).await?;
println!("{}: h-index {}",
author.display_name.as_deref().unwrap_or("?"),
author.summary_stats.as_ref().and_then(|s| s.h_index).unwrap_or(0));Sourcepub async fn get_source(
&self,
id: &str,
params: &GetParams,
) -> Result<Source, OpenAlexError>
pub async fn get_source( &self, id: &str, params: &GetParams, ) -> Result<Source, OpenAlexError>
Get a single publishing venue. Returns ISSNs, OA status, DOAJ membership, APC pricing, host publisher hierarchy, impact metrics, and publication year range.
GET /sources/{id}
Accepts: OpenAlex ID (S...), ISSN.
§Example
use papers_openalex::{OpenAlexClient, GetParams};
let client = OpenAlexClient::new();
let source = client.get_source("S137773608", &GetParams::default()).await?;
println!("{} ({})", source.display_name.as_deref().unwrap_or("?"),
source.r#type.as_deref().unwrap_or("unknown"));Sourcepub async fn get_institution(
&self,
id: &str,
params: &GetParams,
) -> Result<Institution, OpenAlexError>
pub async fn get_institution( &self, id: &str, params: &GetParams, ) -> Result<Institution, OpenAlexError>
Get a single research institution. Returns ROR ID, geographic coordinates, parent/child institution relationships, hosted repositories, and research output metrics.
GET /institutions/{id}
Accepts: OpenAlex ID (I...), ROR.
§Example
use papers_openalex::{OpenAlexClient, GetParams};
let client = OpenAlexClient::new();
let inst = client.get_institution("I136199984", &GetParams::default()).await?;
// Also accepts ROR:
let inst = client.get_institution("https://ror.org/03vek6s52", &GetParams::default()).await?;Sourcepub async fn get_topic(
&self,
id: &str,
params: &GetParams,
) -> Result<Topic, OpenAlexError>
pub async fn get_topic( &self, id: &str, params: &GetParams, ) -> Result<Topic, OpenAlexError>
Get a single research topic. Returns AI-generated description, keywords, position in domain > field > subfield hierarchy, sibling topics, and work counts.
GET /topics/{id}
Accepts: OpenAlex ID (T...).
§Example
use papers_openalex::{OpenAlexClient, GetParams};
let client = OpenAlexClient::new();
let topic = client.get_topic("T10001", &GetParams::default()).await?;
println!("{}: {} works",
topic.display_name.as_deref().unwrap_or("?"),
topic.works_count.unwrap_or(0));Sourcepub async fn get_publisher(
&self,
id: &str,
params: &GetParams,
) -> Result<Publisher, OpenAlexError>
pub async fn get_publisher( &self, id: &str, params: &GetParams, ) -> Result<Publisher, OpenAlexError>
Get a single publisher. Returns hierarchy level, parent publisher, country codes, linked sources, and citation metrics.
GET /publishers/{id}
Accepts: OpenAlex ID (P...).
§Example
use papers_openalex::{OpenAlexClient, GetParams};
let client = OpenAlexClient::new();
let publisher = client.get_publisher("P4310319965", &GetParams::default()).await?;
println!("{}: {} works",
publisher.display_name.as_deref().unwrap_or("?"),
publisher.works_count.unwrap_or(0));Sourcepub async fn get_domain(
&self,
id: &str,
params: &GetParams,
) -> Result<Domain, OpenAlexError>
pub async fn get_domain( &self, id: &str, params: &GetParams, ) -> Result<Domain, OpenAlexError>
Get a single research domain. Returns description, child fields, sibling domains, and work/citation counts.
GET /domains/{id}
Accepts: numeric ID (e.g. "3" for Physical Sciences).
§Example
use papers_openalex::{OpenAlexClient, GetParams};
let client = OpenAlexClient::new();
let domain = client.get_domain("3", &GetParams::default()).await?;
println!("{}: {} fields",
domain.display_name.as_deref().unwrap_or("?"),
domain.fields.as_ref().map(|f| f.len()).unwrap_or(0));Sourcepub async fn get_field(
&self,
id: &str,
params: &GetParams,
) -> Result<Field, OpenAlexError>
pub async fn get_field( &self, id: &str, params: &GetParams, ) -> Result<Field, OpenAlexError>
Get a single academic field. Returns parent domain, child subfields, sibling fields, and work/citation counts.
GET /fields/{id}
Accepts: numeric ID (e.g. "17" for Computer Science).
§Example
use papers_openalex::{OpenAlexClient, GetParams};
let client = OpenAlexClient::new();
let field = client.get_field("17", &GetParams::default()).await?;
println!("{}: {} subfields",
field.display_name.as_deref().unwrap_or("?"),
field.subfields.as_ref().map(|s| s.len()).unwrap_or(0));Sourcepub async fn get_subfield(
&self,
id: &str,
params: &GetParams,
) -> Result<Subfield, OpenAlexError>
pub async fn get_subfield( &self, id: &str, params: &GetParams, ) -> Result<Subfield, OpenAlexError>
Get a single research subfield. Returns parent field and domain, child topics, sibling subfields, and work/citation counts.
GET /subfields/{id}
Accepts: numeric ID (e.g. "1702" for Artificial Intelligence).
§Example
use papers_openalex::{OpenAlexClient, GetParams};
let client = OpenAlexClient::new();
let subfield = client.get_subfield("1702", &GetParams::default()).await?;
println!("{}: {} topics",
subfield.display_name.as_deref().unwrap_or("?"),
subfield.topics.as_ref().map(|t| t.len()).unwrap_or(0));Sourcepub async fn get_funder(
&self,
id: &str,
params: &GetParams,
) -> Result<Funder, OpenAlexError>
pub async fn get_funder( &self, id: &str, params: &GetParams, ) -> Result<Funder, OpenAlexError>
Get a single funder. Returns Wikidata description, Crossref funder ID, grant/award counts, country, and research impact metrics.
GET /funders/{id}
Accepts: OpenAlex ID (F...).
§Example
use papers_openalex::{OpenAlexClient, GetParams};
let client = OpenAlexClient::new();
let funder = client.get_funder("F4320332161", &GetParams::default()).await?;
println!("{} ({}): {} awards",
funder.display_name.as_deref().unwrap_or("?"),
funder.country_code.as_deref().unwrap_or("?"),
funder.awards_count.unwrap_or(0));Sourcepub async fn autocomplete_works(
&self,
q: &str,
) -> Result<AutocompleteResponse, OpenAlexError>
pub async fn autocomplete_works( &self, q: &str, ) -> Result<AutocompleteResponse, OpenAlexError>
Autocomplete for works. Searches titles. Returns up to 10 results sorted by citation count. Hint shows first author name.
GET /autocomplete/works?q=...
§Example
use papers_openalex::OpenAlexClient;
let client = OpenAlexClient::new();
let response = client.autocomplete_works("machine learning").await?;
for result in &response.results {
println!("{} (by {})", result.display_name,
result.hint.as_deref().unwrap_or("unknown author"));
}Autocomplete for authors. Searches display names. Returns up to 10 results sorted by citation count. Hint shows last known institution and country.
GET /autocomplete/authors?q=...
§Example
use papers_openalex::OpenAlexClient;
let client = OpenAlexClient::new();
let response = client.autocomplete_authors("einstein").await?;
for result in &response.results {
println!("{} — {}", result.display_name,
result.hint.as_deref().unwrap_or(""));
}Sourcepub async fn autocomplete_sources(
&self,
q: &str,
) -> Result<AutocompleteResponse, OpenAlexError>
pub async fn autocomplete_sources( &self, q: &str, ) -> Result<AutocompleteResponse, OpenAlexError>
Autocomplete for sources (journals, repositories). Searches display names. Returns up to 10 results sorted by citation count. Hint shows host organization. External ID is ISSN.
GET /autocomplete/sources?q=...
§Example
use papers_openalex::OpenAlexClient;
let client = OpenAlexClient::new();
let response = client.autocomplete_sources("nature").await?;
for result in &response.results {
println!("{} ({})", result.display_name,
result.hint.as_deref().unwrap_or(""));
}Sourcepub async fn autocomplete_institutions(
&self,
q: &str,
) -> Result<AutocompleteResponse, OpenAlexError>
pub async fn autocomplete_institutions( &self, q: &str, ) -> Result<AutocompleteResponse, OpenAlexError>
Autocomplete for institutions. Searches display names. Returns up to 10 results sorted by citation count. Hint shows city and country. External ID is ROR.
GET /autocomplete/institutions?q=...
§Example
use papers_openalex::OpenAlexClient;
let client = OpenAlexClient::new();
let response = client.autocomplete_institutions("harvard").await?;
for result in &response.results {
println!("{} — {}", result.display_name,
result.hint.as_deref().unwrap_or(""));
}Sourcepub async fn autocomplete_publishers(
&self,
q: &str,
) -> Result<AutocompleteResponse, OpenAlexError>
pub async fn autocomplete_publishers( &self, q: &str, ) -> Result<AutocompleteResponse, OpenAlexError>
Autocomplete for publishers. Searches display names. Returns up to 10 results sorted by citation count. Hint shows country.
GET /autocomplete/publishers?q=...
Note: this endpoint has been observed returning HTTP 500 errors intermittently (server-side issue).
§Example
use papers_openalex::OpenAlexClient;
let client = OpenAlexClient::new();
let response = client.autocomplete_publishers("elsevier").await?;
for result in &response.results {
println!("{} ({})", result.display_name,
result.hint.as_deref().unwrap_or(""));
}Sourcepub async fn autocomplete_subfields(
&self,
q: &str,
) -> Result<AutocompleteResponse, OpenAlexError>
pub async fn autocomplete_subfields( &self, q: &str, ) -> Result<AutocompleteResponse, OpenAlexError>
Autocomplete for subfields. Searches display names. Returns up to 10 results sorted by citation count. Hint shows description.
GET /autocomplete/subfields?q=...
Note: autocomplete is only available for subfields, not domains or
fields (/autocomplete/domains and /autocomplete/fields return 404).
§Quirks
The subfield autocomplete endpoint returns entity_type: null and
short_id: "Nones/..." — these are known API quirks. The
AutocompleteResult fields are
Option<String> to handle this.
§Example
use papers_openalex::OpenAlexClient;
let client = OpenAlexClient::new();
let response = client.autocomplete_subfields("artificial").await?;
for result in &response.results {
println!("{} — {}", result.display_name,
result.hint.as_deref().unwrap_or(""));
}Sourcepub async fn autocomplete_funders(
&self,
q: &str,
) -> Result<AutocompleteResponse, OpenAlexError>
pub async fn autocomplete_funders( &self, q: &str, ) -> Result<AutocompleteResponse, OpenAlexError>
Autocomplete for funders. Searches display names. Returns up to 10 results sorted by citation count. Hint shows country and description.
GET /autocomplete/funders?q=...
§Example
use papers_openalex::OpenAlexClient;
let client = OpenAlexClient::new();
let response = client.autocomplete_funders("national science").await?;
for result in &response.results {
println!("{} ({})", result.display_name,
result.hint.as_deref().unwrap_or(""));
}Sourcepub async fn find_works(
&self,
params: &FindWorksParams,
) -> Result<FindWorksResponse, OpenAlexError>
pub async fn find_works( &self, params: &FindWorksParams, ) -> Result<FindWorksResponse, OpenAlexError>
Semantic search for works via GET. Sends query as a query parameter. Returns works ranked by AI similarity score (0-1). Maximum 10,000 character query. Requires API key. Costs 1,000 credits per request.
GET /find/works?query=...
§Example
use papers_openalex::{OpenAlexClient, FindWorksParams};
let client = OpenAlexClient::with_api_key("your-key");
let params = FindWorksParams::builder()
.query("machine learning for drug discovery")
.count(5)
.build();
let response = client.find_works(¶ms).await?;
for result in &response.results {
println!("Score {:.2}: {}", result.score,
result.work.get("display_name")
.and_then(|v| v.as_str())
.unwrap_or("?"));
}Sourcepub async fn find_works_post(
&self,
params: &FindWorksParams,
) -> Result<FindWorksResponse, OpenAlexError>
pub async fn find_works_post( &self, params: &FindWorksParams, ) -> Result<FindWorksResponse, OpenAlexError>
Semantic search for works via POST. Sends query in JSON body as
{"query": "..."}, useful for long queries exceeding URL length limits.
Same response format as find_works. Requires API
key. Costs 1,000 credits per request.
POST /find/works
§Example
use papers_openalex::{OpenAlexClient, FindWorksParams};
let client = OpenAlexClient::with_api_key("your-key");
let long_query = "A very long research question or abstract text...";
let params = FindWorksParams::builder()
.query(long_query)
.count(10)
.filter("publication_year:>2020")
.build();
let response = client.find_works_post(¶ms).await?;Trait Implementations§
Source§impl Clone for OpenAlexClient
impl Clone for OpenAlexClient
Source§fn clone(&self) -> OpenAlexClient
fn clone(&self) -> OpenAlexClient
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more