# OpenAlex Scraper: Works, Authors & Institutions (`arman-bd/openalex-scraper`) Actor

Scrape the OpenAlex scholarly graph: works with authors, institutions, topics, citation counts, open-access status and funding. Free, unauthenticated, no proxy.

- **URL**: https://apify.com/arman-bd/openalex-scraper.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** Developer tools, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.37 / 1,000 entity scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## OpenAlex Scraper: Works, Authors & Institutions

![OpenAlex Scraper: Works, authors, institutions, topics, citation counts, open-access status and funding from the open scholarly graph](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/openalex-scraper.jpg)

Exports the open scholarly graph: 320M+ works with their authors, institutions, topics, citation counts, open-access status and funding, plus the author, institution, source, funder and publisher records those works link to.

OpenAlex is the open replacement for Microsoft Academic Graph. This Actor reads it directly, so there's no browser, no proxy and no authentication. Paging is handled for you, including past the offset ceiling that would otherwise cap a large result set.

**Agent skill: [SKILL.md](https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/openalex-scraper.md)**

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/openalex-scraper.md
```

### What you get

Every record carries the same key set regardless of entity type, so a mixed dataset still loads into one table. Fields that do not apply are `null`.

| Field | What it holds |
|---|---|
| `entityType`, `openalexId`, `shortId`, `url` | Which collection the record came from, its full OpenAlex URI and the bare ID (`W2101234009`) |
| `displayName`, `title` | Entity name, and for works the paper title |
| `doi`, `landingPageUrl` | DOI and publisher landing page |
| `publicationYear`, `publicationDate`, `type`, `language`, `isRetracted` | Work metadata |
| `openAccessStatus`, `isOpenAccess`, `oaUrl` | OA colour (`gold`, `green`, `bronze`, `hybrid`, `closed`), flag, and free full-text URL |
| `sourceName`, `sourceType`, `sourceIssn`, `hostOrganization` | The journal, repository or conference the work appeared in |
| `authorships`, `authorCount` | Author name, ORCID, position, corresponding flag, affiliations and countries. Capped at the first 100 authors |
| `institutions` | Distinct affiliated institutions across all authors |
| `concepts`, `topics` | Legacy concepts with scores, plus the current topic, field and domain taxonomy |
| `citedByCount`, `worksCount`, `referencedWorksCount` | Citation and output counts |
| `fundingGrants` | Funder name, funder ID and grant number per award |
| `orcid`, `ror`, `countryCode`, `homepageUrl` | Identifiers for author, institution and source records |
| `hIndex`, `i10Index`, `twoYearMeanCitedness` | OpenAlex summary statistics |
| `lastKnownInstitutions` | Current affiliations on author records |
| `searchQuery`, `updatedDate`, `scrapedAt` | Which query produced the record, when OpenAlex last touched it, and when this run read it |

`RUN_SUMMARY` in the key-value store holds per-run counts, the filters you used, and the outcome of every query: the ones that failed, the ones that matched nothing, and the ones skipped because the run's cap was already spent.

### Use cases

- **Institutional output.** Filter on a ROR ID and count works per year and topic.
- **Collaborator discovery.** Pull works on a topic, then group by author and affiliation.
- **Open-access tracking.** Count `openAccessStatus` by publisher, funder or year.
- **Systematic reviews.** Run several search strings, dedupe on `doi`, and export to CSV for screening.
- **RAG and knowledge bases.** Clean DOI-keyed metadata that joins to Crossref, PubMed and ORCID.

### Quick start

One search, default cap:

```json
{
 "entityType": "works",
 "searchQueries": ["large language models"]
}
```

Recent open-access articles across two topics, tagged per query:

```json
{
 "entityType": "works",
 "searchQueries": ["crispr gene editing", "base editing"],
 "filters": {
 "publication_year": ">2022",
 "is_oa": "true",
 "type": "article"
 },
 "mailto": "you@example.com",
 "maxResults": 1000
}
```

Everything one institution published last year, no free-text search at all:

```json
{
 "entityType": "works",
 "filters": {
 "authorships.institutions.ror": "https://ror.org/042nb2s44",
 "publication_year": "2025"
 },
 "mailto": "you@example.com",
 "maxResults": 5000
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `entityType` | string | `works` | `works`, `authors`, `institutions`, `sources`, `concepts`, `topics`, `funders` or `publishers`. |
| `searchQueries` | array | `[]` | One pass per query, results tagged with the query. Empty means a single unfiltered sweep. The same query written twice is run once. |
| `filters` | object | `{}` | Key/value OpenAlex filter expressions, joined into one `filter=` parameter. |
| `mailto` | string | `""` | Contact email sent with each request, identifying you as the operator. Worth setting on large runs. |
| `maxResults` | integer | `200` | The most records the **whole run** may return, across every query. Must be 1 or more — there is no unlimited value. |

The Actor runs green with no input at all: it returns the first 200 works OpenAlex serves.

`maxResults` is the run total and the number you are billed against, never a per-query allowance. Three queries at `maxResults: 300` return 300 records, not 900: the budget is split evenly across the queries and recomputed from what has actually been delivered, so a query that runs out early leaves its share to the ones after it. If the budget is spent before the last query is reached, those queries are named in `RUN_SUMMARY.targetsSkipped` rather than dropped silently. A record already returned by an earlier query is not returned — or charged — a second time; the count is in `RUN_SUMMARY.duplicatesSkipped`.

Filter values pass through untouched, so OpenAlex operators work: `">2020"`, `"!null"`, `"2020-2024"`. Array values are joined with `|`, which OpenAlex reads as OR, so `{"type": ["article", "preprint"]}` becomes `type:article|preprint`. An unknown filter key returns HTTP 400 and OpenAlex lists every valid field in the error, which the Actor copies into `RUN_SUMMARY.failures`.

### Output example

```json
{
 "entityType": "works",
 "openalexId": "https://openalex.org/W4404534210",
 "shortId": "W4404534210",
 "displayName": "A Survey on Hallucination in Large Language Models: Principles, Taxonomy, Challenges, and Open Questions",
 "title": "A Survey on Hallucination in Large Language Models: Principles, Taxonomy, Challenges, and Open Questions",
 "doi": "https://doi.org/10.1145/3703155",
 "publicationYear": 2024,
 "publicationDate": "2024-11-20",
 "type": "article",
 "language": "en",
 "isRetracted": false,
 "openAccessStatus": "bronze",
 "isOpenAccess": true,
 "oaUrl": "https://dl.acm.org/doi/pdf/10.1145/3703155",
 "landingPageUrl": "https://doi.org/10.1145/3703155",
 "sourceName": "ACM Transactions on Information Systems",
 "sourceType": "journal",
 "sourceIssn": "1046-8188",
 "hostOrganization": null,
 "authorships": [
 {
 "name": "Weijiang Yu",
 "authorId": "A5055989750",
 "orcid": "https://orcid.org/0000-0002-7449-3093",
 "position": "middle",
 "isCorresponding": false,
 "institutions": ["Huawei Technologies (China)"],
 "countries": ["CN"]
 }
 ],
 "authorCount": 11,
 "institutions": ["Harbin Institute of Technology", "Huawei Technologies (China)"],
 "concepts": [{ "name": "Visual Hallucination", "level": 2, "score": 0.564 }],
 "topics": [
 { "name": "Topic Modeling", "field": "Computer Science", "domain": "Physical Sciences", "score": 0.9926 }
 ],
 "citedByCount": 1757,
 "referencedWorksCount": 214,
 "fundingGrants": [],
 "updatedDate": "2026-08-05T07:39:15.569665",
 "url": "https://openalex.org/W4404534210",
 "searchQuery": "large language models",
 "scrapedAt": "2026-08-06T11:43:13.229Z"
}
```

An `authors` record fills a different subset: `worksCount`, `hIndex`, `i10Index`, `twoYearMeanCitedness`, `lastKnownInstitutions`, and a `topics` list where each entry carries a `works` count rather than a relevance `score`.

### Writing filters

Filters are where the real power is. A few that come up constantly:

| Goal | Filter |
|---|---|
| Works from one institution | `{"authorships.institutions.ror": "https://ror.org/042nb2s44"}` |
| Works by one author | `{"author.orcid": "https://orcid.org/0000-0002-7449-3093"}` |
| Published since 2022 | `{"publication_year": ">2021"}` |
| Journal articles only | `{"type": "article"}` |
| Open access only | `{"is_oa": "true"}` |
| In one journal | `{"primary_location.source.issn": "1046-8188"}` |
| Highly cited | `{"cited_by_count": ">100"}` |
| Funded by NSF | `{"awards.funder_id": "F4320306076"}` |

To find an institution's ROR or a funder's OpenAlex ID, run the Actor once with `entityType: "institutions"` or `"funders"` and a search query. The `ror` and `shortId` fields it returns are exactly what the filters above expect.

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~openalex-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "entityType": "works",
 "searchQueries": ["retrieval augmented generation"],
 "filters": { "publication_year": ">2023", "is_oa": "true" },
 "mailto": "you@example.com",
 "maxResults": 500
 }'
```

### JavaScript example

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/openalex-scraper').call({
 entityType: 'works',
 filters: { 'authorships.institutions.ror': 'https://ror.org/042nb2s44', publication_year: '2025' },
 mailto: 'you@example.com',
 maxResults: 2000,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
const oaShare = items.filter((w) => w.isOpenAccess).length / items.length;
console.log(`${items.length} works, ${(oaShare * 100).toFixed(1)}% open access`);
```

### Notes

- Paging is always cursor-based. Every query starts at `cursor=*` and follows `meta.next_cursor`. Offset paging silently caps at 10,000 records and cursor paging does not, so `maxResults` is the only limit that applies.
- Each request returns 200 records, which is OpenAlex's maximum. A 5,000-record query is 25 requests.
- Setting `mailto` is worth it on large runs. Your address is sent with each request and carried in the User-Agent, which is what the OpenAlex docs ask of automated clients.
- Author lists are capped at 100. Some consortium papers list thousands of authors, and `authorCount` always reports the true total.
- One bad query won't kill the run. A 400 or 404 is recorded in `RUN_SUMMARY.failures` and the remaining queries continue, delivering their records as normal. The Actor throws only when every query failed and nothing was returned.
- A query that matched nothing is named too, in `RUN_SUMMARY.emptyQueries`, with its match count in `RUN_SUMMARY.queries[]`. Zero rows from a query you ran and zero rows from a query that was never reached look identical in a dataset; the summary is what tells them apart.
- The cap is checked before anything is fetched. `maxResults` below 1, fractional or unreadable stops the run immediately with an error naming the field, rather than being read as "no limit" — nothing is fetched and nothing is charged.
- Transient errors get retried. A throttled or 5xx response is retried up to three times with growing backoff, honouring the wait the response asks for up to a 30-second ceiling, and each request has a 60-second timeout because a full 200-record page can take ten seconds or more. A request the source rejects outright, and a payload that is not the expected shape, are final for that query and are recorded rather than retried.

### FAQ

**Do I need an API key?** No. You supply no credentials.

**Do I need a proxy?** No. Proxy configuration is not required to run this Actor.

**How many records can one run return?** Exactly as many as you set `maxResults` to, across all queries together — that is the number you are billed against. Cursor paging has no ceiling, so multi-hundred-thousand-record exports are a matter of run time and the cap you set, not API limits.

**Can I get abstracts?** OpenAlex ships abstracts as an inverted index rather than text, and licensing varies by source, so this Actor does not emit them. Use `oaUrl` or `landingPageUrl` to fetch the full text where it is openly available.

**Concepts or topics?** Topics are the current taxonomy (topic, then subfield, field and domain) and are what OpenAlex maintains. Concepts are the legacy Microsoft Academic Graph vocabulary, kept for continuity. Both are returned for works.

**Can I schedule it?** Yes. Diff on `openalexId` plus `updatedDate` to pick up only records OpenAlex has revised since the last run.

# Actor input Schema

## `entityType` (type: `string`):

Which OpenAlex collection to read. 'works' returns papers, preprints, books and datasets; the others return the graph nodes those works link to.

## `searchQueries` (type: `array`):

Free-text searches. Each query is run separately and its results are tagged with searchQuery in the output. Leave empty to return everything the filters match.

## `filters` (type: `object`):

OpenAlex filter expressions as key/value pairs, for example {"publication\_year": "2024", "is\_oa": "true"}. Values can use OpenAlex operators (>2020, !null) and arrays are joined with | as an OR.

## `mailto` (type: `string`):

Optional. Sent with each request to identify you as the operator, which is what the source asks of automated clients. Leave empty to run without one.

## `maxResults` (type: `integer`):

The most records the run may return in total, across every query — this is what you are billed for. The budget is shared evenly between the queries that still have results left. Must be 1 or more; there is no 'unlimited' value.

## Actor input object example

```json
{
  "entityType": "works",
  "searchQueries": [
    "crispr gene editing",
    "quantum error correction"
  ],
  "filters": {
    "publication_year": ">2022",
    "is_oa": "true",
    "type": "article"
  },
  "mailto": "you@example.com",
  "maxResults": 200
}
```

# Actor output Schema

## `items` (type: `string`):

Every record the run produced.

## `runsummary` (type: `string`):

The RUN\_SUMMARY record from the run's key-value store.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "entityType": "works",
    "searchQueries": [
        "large language models"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/openalex-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "entityType": "works",
    "searchQueries": ["large language models"],
}

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/openalex-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "entityType": "works",
  "searchQueries": [
    "large language models"
  ]
}' |
apify call arman-bd/openalex-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/openalex-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/IPhkaYWO0km9JNdTy/builds/NEEWAkSBcUOX3NlDH/openapi.json
