# OpenAlex Academic Paper Search & DOI Lookup (`miacx/academic-paper-search`) Actor

Search OpenAlex papers or look up DOIs in batches. Get authors, abstracts when available, citation counts and open-access links. Full text is separate.

- **URL**: https://apify.com/miacx/academic-paper-search.md
- **Developed by:** [mia.cx](https://apify.com/miacx) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## OpenAlex Academic Paper Search & DOI Lookup

Give an agent paper metadata and available abstracts without parsing search pages.
Search OpenAlex or send a batch of DOIs to get authors, citations, topics and open-access links.
Full text is separate; PDF links are not downloaded or verified.
Each successful paper costs $0.0005, plus $0.00005 per run start at default memory.

### Try it

```json
{"dois":["10.1038/nature12373"]}
```

Empty input `{}` searches for `retrieval augmented generation` and returns up to ten papers.
The Actor reads OpenAlex. No upstream credential field is required in the input.

### Use from an agent

Add `https://mcp.apify.com?tools=miacx/academic-paper-search` to your MCP client and sign in to Apify.
Call the dedicated Actor tool, then use `get-dataset-items` to read the completed run's dataset.
Or set `APIFY_TOKEN` to your Apify API token and call the API:

```sh
curl --fail-with-body 'https://api.apify.com/v2/actors/miacx~academic-paper-search/run-sync-get-dataset-items' \
  -H "Authorization: Bearer $APIFY_TOKEN" -H 'Content-Type: application/json' \
  -d '{"dois":["10.1038/nature12373"]}'
```

### What you get

The dataset contains one row per paper, plus rows for invalid inputs, missing DOIs or source failures.
Read `status` first. Only `ok` rows incur Paper events; missing metadata can still occur on successful rows.
Nonempty `dois` selects DOI lookup instead of search.

Selected fields from replaying the recorded `tests/fixtures/doi.json` response:

```json
{
  "status": "ok",
  "input": "10.1038/nature12373",
  "openAlexId": "https://openalex.org/W2159974629",
  "doi": "https://doi.org/10.1038/nature12373",
  "title": "Nanometre-scale thermometry in a living cell",
  "year": 2013,
  "venue": "Nature",
  "type": "article",
  "citationCount": 1961,
  "isOpenAccess": true,
  "openAccessStatus": "bronze",
  "pdfUrl": "https://www.nature.com/articles/nature12373.pdf"
}
```

This is fixture output, not a fresh lookup. Citation counts and links can change.
Rows also contain `source`, `fetchedAt`, `reason`, `authors`, `abstract`, `landingUrl`, `topics` and `concepts`.
`source` is the OpenAlex request URL without credentials. `reason` is null on success.

Missing metadata stays null. Error rows keep the same fields with null metadata and empty arrays.
`authors` contains `{name, orcid}` objects in source order; each value can be null.
`topics` and legacy `concepts` contain `{id, name, score}` objects in source order; missing scores are null.
These arrays are not deduplicated. Concepts are deprecated upstream; topics are the current classification.

Abstracts reconstruct OpenAlex's inverted index literally, without rewriting. Missing indexes produce null abstracts.
`pdfUrl` comes only from an open-access best location. An open-access label does not guarantee a PDF.
`landingUrl` prefers the best OA landing page, then primary landing page, OA URL and DOI.
Fetch full text separately.

### Input

| Field | Default | Accepted input |
| --- | --- | --- |
| `query` | `"retrieval augmented generation"` | 1–500 characters after trimming. Used when `dois` is empty. |
| `dois` | `[]` | Up to 500 DOI strings, `doi:` identifiers or doi.org URLs. Nonempty selects DOI lookup. |
| `yearFrom` | `0` | Inclusive first publication year, 1500–2200; 0 means no lower bound. |
| `yearTo` | `0` | Inclusive last publication year, 1500–2200; 0 means no upper bound. |
| `openAccessOnly` | `false` | Restrict search to works OpenAlex marks open access. |
| `type` | `"all"` | `all`, `article`, `review`, `preprint`, `book`, `book-chapter`, `dataset` or `dissertation`. |
| `minCitations` | `0` | Nonnegative integer; inclusive minimum OpenAlex citation count. |
| `sort` | `"relevance"` | `relevance`, `citations` (most cited), `newest` or `oldest` by publication date. |
| `maxResults` | `10` | 1–500 unique successful search rows, subject to the run budget. |

Nonempty `dois` overrides query, search filters, sort and `maxResults`. All fields still validate before either mode runs.
Valid DOIs normalize case and prefixes; duplicates merge within the run.
Each unique valid DOI produces a paper or error row. Invalid entries each produce `invalid_doi` before valid lookup results.
Valid DOI results follow first-seen input order, not citation or date order.

Normalized DOIs allow up to 250 characters. Whitespace, quotes, pipes and control characters are rejected.
Nonzero year bounds must be ordered. Empty or whitespace-only queries are invalid, even in DOI mode.
See the [input schema](.actor/input_schema.json) for the complete contract.

To search recent open-access reviews:

```json
{"query":"battery recycling","yearFrom":2022,"openAccessOnly":true,"type":"review","minCitations":10,"sort":"citations","maxResults":20}
```

### Pricing

A Paper costs $0.0005 and counts one successful row, including cached results and papers with missing metadata.
Search results deduplicate OpenAlex work IDs; DOI mode deduplicates normalized valid DOI inputs within one run.
Errors and no-result rows incur no Paper events.

One thousand Paper events cost $0.50, plus $0.00005 per run start at default memory.
A ten-paper search costs $0.00505 including startup. Default memory is 256 MB.
Apify counts one start event per GB of memory, with a minimum of one event.
The $0.00005 run start at default memory applies even with no billable papers.

The Actor checks remaining event capacity before fetching and emits only the charged subset of a batch.
A budget below one Paper event returns `budget_reached`. Increase the budget or request fewer papers.

### Limits and data freshness

Search uses at most five pages of up to 100 results. DOI lookups group up to 100 unique values per request.
OpenAlex coverage can omit papers, abstracts and metadata. An empty search returns `no_results`.
A later page can fail after earlier papers succeeded; inspect every row.

The upstream deadline is 25 seconds, with eight-second request timeouts and up to three transient-failure attempts.
OpenAlex rate limits and quota apply. The operator can configure an OpenAlex key; the Actor has no credential input field.
No browser or runtime LLM calls are used.

Search responses can be reused for one hour; identical DOI batches for 24 hours.
Overlapping DOI batches with different grouping do not share cache entries.
The named store `paper-search-openalex-cache-v1` keeps at most 256 hashed response slots; collisions cause misses.
`fetchedAt` marks row production time, including cache hits, and does not prove a fresh OpenAlex fetch.

### If a row fails

| Status | Next step |
| --- | --- |
| `invalid_input` | Read `reason`; correct the query, field values or year bounds. |
| `invalid_doi` | Supply a valid DOI or doi.org URL without unsupported delimiter characters. |
| `no_results` | Widen search filters or check the DOI. OpenAlex may not cover the work. |
| `budget_reached` | Raise the run budget or reduce the requested work. |
| `upstream_429`, `timeout` | Retry later after checking upstream quota and rate limits. |
| `dns_error`, `network_error`, `upstream_5xx` | Retry later; the source could not complete the request. Actual HTTP codes appear in the status. |
| `upstream_invalid_response` | Report the input and reason; OpenAlex returned an unexpected shape. |
| Other `upstream_<HTTP status>` | Read `reason` for the source response. |

Field definitions are in the [dataset schema](.actor/dataset_schema.json).

# Actor input Schema

## `query` (type: `string`):

OpenAlex search keywords, 1–500 characters after trimming. Defaults to retrieval augmented generation. Used when dois is empty or omitted; a nonempty DOI batch overrides the search. Empty or whitespace-only queries are invalid, including in DOI mode.

## `dois` (type: `array`):

Up to 500 DOI strings, doi: identifiers or doi.org URLs. Defaults to \[]; empty uses query search. Nonempty overrides query, filters, sort and maxResults, though all fields still validate. Valid DOIs normalize to lowercase and merge within a run. Each invalid entry or missing unique DOI returns an unbilled error row. Normalized DOI length is at most 250 characters.

## `yearFrom` (type: `integer`):

Inclusive first publication year, 1500–2200, or 0 (default) for no lower bound. Must not exceed a nonzero yearTo. Applies to search results only; still validated in DOI mode.

## `yearTo` (type: `integer`):

Inclusive last publication year, 1500–2200, or 0 (default) for no upper bound. Must not precede a nonzero yearFrom. Applies to search results only; still validated in DOI mode.

## `openAccessOnly` (type: `boolean`):

true restricts search to papers OpenAlex marks open access; false (default) includes closed works. Does not guarantee a PDF or verify links. Ignored for DOI lookups.

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

Search work type: all (default), article, review, preprint, book, book-chapter, dataset or dissertation. all adds no type filter. Ignored for DOI lookups.

## `minCitations` (type: `integer`):

Nonnegative integer, default 0. Inclusive minimum number of citing works recorded by OpenAlex. Search only; DOI lookups ignore this filter.

## `sort` (type: `string`):

Search order: relevance (default), citations (most cited first), newest or oldest by publication date. Ignored for DOI lookups; valid DOI results follow first-seen input order.

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

Integer from 1 to 500, default 10. Maximum unique successful search rows across at most five pages; the run budget can reduce output. DOI mode ignores this cap and returns a row per unique valid DOI plus invalid-entry errors. Each successful row incurs one Paper event.

## Actor input object example

```json
{
  "query": "retrieval augmented generation",
  "dois": [],
  "yearFrom": 0,
  "yearTo": 0,
  "openAccessOnly": false,
  "type": "all",
  "minCitations": 0,
  "sort": "relevance",
  "maxResults": 10
}
```

# Actor output Schema

## `results` (type: `string`):

One row per item, each with a status field.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("miacx/academic-paper-search").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("miacx/academic-paper-search").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 '{}' |
apify call miacx/academic-paper-search --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,miacx/academic-paper-search"
        }
    }
}
```

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/mqjZxKTG0QfprAXzF/builds/17AhzC2iPKKc7a1cq/openapi.json
