# Zenodo Scraper · Research Records, DOIs, Authors & Files (`reapx/zenodo-scraper`) Actor

Scrape open research records, DOIs, publications, datasets, software, authors, and file metadata from Zenodo. Fast HTTP scraper charging per returned record with tiered pricing.

- **URL**: https://apify.com/reapx/zenodo-scraper.md
- **Developed by:** [Tarek Etman](https://apify.com/reapx) (community)
- **Categories:** Developer tools, Business, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.30 / 1,000 record returneds

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

![reapX — public sources in, addressable records out](https://reapx.dev/reapx.gif)

## Zenodo Scraper · Research Records, DOIs, Authors & Files

The **Zenodo Scraper** extracts open research records, DOIs, publications, scientific datasets, software packages, author lists, licensing terms, and direct file download URLs from [Zenodo](https://zenodo.org/), the open-access research repository operated by CERN. It runs over fast HTTP API endpoints with no headless browser overhead, returning structured data ready for bibliometrics, literature reviews, open science tracking, and AI dataset curation.

> Maintained by **reapX**. Every row cites the Apify run that produced it — nothing is
> inferred, modelled or filled in, and a field absent from the source is absent from the row.
> The extracted archive for this source is browsable at
> [reapx.dev/data/zenodo-scraper/](https://reapx.dev/data/zenodo-scraper/) and mirrored as an open dataset on
> [Hugging Face](https://huggingface.co/datasets/reapxdev/zenodo-scraper) and
> [Kaggle](https://www.kaggle.com/datasets/reapxdev/zenodo-scraper). Questions: reapxdev@proton.me

*Maintained by **reapx** (reapxdev@proton.me). Public sources in, addressable records out.*

***

### Features

- 🔬 **Complete Record Metadata**: Extract Digital Object Identifiers (DOIs), concept DOIs, titles, abstracts, publication dates, and resource classifications.
- 👥 **Author & Creator Disambiguation**: Collect full list of authors, creators, affiliations, and subject taxonomy keywords.
- 📁 **File & Dataset Discovery**: Access direct download links, file sizes, MD5 checksums, and total view/download statistics.
- 🏷️ **Community & License Filters**: Filter by Zenodo communities (`biosyslit`, `eu`, `cern`, etc.), licensing (`cc-by-4.0`, `mit`, `cc0-1.0`), and publication year ranges.
- ⚡ **Fast & Cost Efficient**: Uses direct HTTP API queries with exponential backoff on rate limits (HTTP 429), charging strictly per returned record on a Pay-Per-Event model.

***

### ⬇️ Input

Every field is described here exactly as the Actor reads it. Run it with the prefilled input and it returns real rows without any further configuration.

| Field | Type | Required | Prefilled | What it does |
| --- | --- | --- | --- | --- |
| `query` | string | no | `"climate change"` | Full-text search query across titles, abstracts, subjects, and authors (e.g. climate change, machine learning). Leave empty to query all Zenodo records. The broader the query, the more... |
| `community` | string | no | - | Filter records belonging to a specific Zenodo community slug (e.g. biosyslit, eu, ecfunded). Leave this field empty to search across all communities nationwide and worldwide. |
| `resource_type` | string | no | - | Filter by resource type identifier. Select a specific publication type, dataset, software, or image category. Leave empty to retrieve records of all resource types without restrictions. |
| `access_right` | string | no | - | Filter by record access status. Choose open for open-access publications, or filter restricted/embargoed records. Leave empty to return records of any access level. |
| `license` | string | no | - | Filter records by license identifier (e.g. cc-by-4.0, mit, cc0-1.0, apache-2.0). Leave empty to return research records regardless of license. |
| `creator` | string | no | - | Return only records with this person or organisation among the creators (e.g. Hansen, European Commission). Matched against Zenodo's own metadata.creators.person\_or\_org.name field. |
| `file_type` | string | no | - | Return only records that carry at least one file of this type (e.g. pdf, csv, zip, xlsx). Sent as Zenodo's own file\_type facet, not as free text. |
| `year` | string | no | - | Filter by publication year (e.g. 2024) or Lucene year range (e.g. \[2020 TO 2024]). Leave empty to include records published in any year. |
| `date_from` | string | no | - | Only records published on or after this date (e.g. 2024-01-01). Compiled into a Zenodo publication\_date range together with Published To. |
| `date_to` | string | no | - | Only records published on or before this date (e.g. 2024-12-31). |
| `sort` | string | no | `"newest"` | Order Zenodo returns matches in: newest for the latest uploads, bestmatch for relevance to your query, mostrecent for the most recently updated, mostviewed for the most read. |
| `all_versions` | boolean | no | `false` | Return every version of a record, not just the latest one. Zenodo mints a DOI per version and a concept DOI for the record as a whole, and both are on every row. |
| `max_records` | integer | yes | `50` | Maximum number of Zenodo research records to return and write to the dataset (1 to 10,000). The higher the number, the longer the run takes and the more it costs. |

#### What empty means, and what a larger value costs

The two questions an input table normally does not answer. Both columns are read off this Actor's own code and its own price list.

| Field | Left empty | Turned up |
| --- | --- | --- |
| `query` | Leave empty to query all Zenodo records. | Narrowing this reduces rows returned, so it lowers the bill. |
| `community` | Leave this field empty to search across all communities nationwide and worldwide. | Narrowing this reduces rows returned, so it lowers the bill. |
| `resource_type` | Leave empty to retrieve records of all resource types without restrictions. | Narrowing this reduces rows returned, so it lowers the bill. |
| `access_right` | Leave empty to return records of any access level. | Narrowing this reduces rows returned, so it lowers the bill. |
| `license` | Leave empty to return research records regardless of license. | Narrowing this reduces rows returned, so it lowers the bill. |
| `creator` | Leave empty to include records by any author. | The match is on the name string Zenodo holds, so a common surname still returns many people; combine it with a community or a year to narrow it. |
| `file_type` | Leave empty to include records whatever they carry, including metadata-only records with no files at all. | Filtering to pdf is the quickest way to drop metadata-only and embargoed records from a corpus you intend to read. |
| `year` | Leave empty to include records published in any year. | Narrowing this reduces rows returned, so it lowers the bill. |
| `date_from` | Leave empty for no lower bound. | A tight window is the cheapest way to make a repeat run return only what is new since the last one. |
| `date_to` | Leave empty for no upper bound, which is what you want for a monitoring run. | Pairs with Published From; either bound works on its own, and both are applied by Zenodo rather than after the fact. |
| `sort` | Falls back to newest. | Ordering decides which records you get when Max Records is smaller than the match count, which it usually is; it does not change what a row costs. |
| `all_versions` | One row per record, the current version. | This genuinely multiplies rows and therefore cost - the same query measured 218,626 matches with it off and 260,002 with it on - so raise Max Records deliberately, not by accident. |
| `max_records` | The prefill `50` is used | Each row returned is one **$0.002** charge, so this field is your cost cap as well as your row cap. |

### ⬆️ Output

One row per record, as JSON, CSV, Excel or XML, and through the API. Every field below is on every row this Actor writes; a field the source did not return is empty rather than guessed.

| Field | Type | What it gives you | Example |
| --- | --- | --- | --- |
| `doi` | string | Digital Object Identifier (DOI) of the Zenodo record (e.g. 10.5281/zenodo.21778617). | "10.5281/zenodo.21778617" |
| `paperId` | string | Standardized paper ID derived from DOI with colon replaced by hyphen (e.g. 10.5281-zenodo.21778617). | "10.5281-zenodo.21778617" |
| `recordId` | string | Unique numerical ID assigned by Zenodo to the record. | "21778617" |
| `conceptDoi` | string | Concept DOI representing all versions of this research record. | "10.5281/zenodo.21778616" |
| `conceptRecId` | string | Concept record ID representing all versions of this item. | "21778616" |
| `title` | string | Title of the research publication, dataset, or software package. | "Comparing and Conceptualizing Data Protection Requiremen... |
| `description` | string | Abstract, summary text, or detailed description of the record. | "This dataset comprises different materials of the projec... |
| `publicationDate` | string | Date when the record was published (YYYY-MM-DD format). | "2026-08-03" |
| `resourceType` | string | Human-readable label of the resource type (e.g. Journal article, Dataset, Software). | "Journal article" |
| `resourceTypeId` | string | Canonical identifier for the resource type. | "publication-article" |
| `creators` | array | List of creators/authors of the research record. | \["Hassan Saleh", "Jane Doe"] |
| `authors` | array | List of author names (alias for creators). | \["Hassan Saleh", "Jane Doe"] |
| `license` | string | License under which the record or data is shared. | "cc-by-4.0" |
| `accessRight` | string | Access status of the record (e.g. open, restricted, embargoed). | "open" |
| `communities` | array | List of Zenodo community identifiers associated with this record. | \["biosyslit", "eu"] |
| `keywords` | array | List of subject keywords attached to the record. | \["privacy", "data protection", "gdpr"] |
| `files` | array | List of attached file objects including filename, size, checksum, and download URL. | \[{"filename": "data.zip", "size": 22301469, "checksum": "... |
| `fileCount` | integer | Total number of files attached to this record. | 1 |
| `downloads` | integer | Number of times files in this record have been downloaded. | 15 |
| `views` | integer | Number of times this record page has been viewed. | 120 |
| `doiUrl` | string | Permanent DOI URL pointing to the record. | "https://doi.org/10.5281/zenodo.21778617" |
| `url` | string | Direct web URL of the record on Zenodo. | "https://zenodo.org/records/21778617" |
| `created` | string | ISO 8601 timestamp when the record was created in Zenodo. | "2026-08-03T18:14:32.401201+00:00" |
| `updated` | string | ISO 8601 timestamp when the record was last updated. | "2026-08-03T18:14:32.656983+00:00" |
| `grantIds` | array | Funder grant numbers attached to the record, such as a Horizon Europe project number. Empty on records with no funding... | \["101071008"] |
| `grantTitles` | array | Title of each funded project the record is attached to. | \["Closed-loop Individualized image-guided Transcranial Ul... |
| `funders` | array | Funding bodies named on the record, e.g. European Commission. | \["European Commission"] |
| `revision` | integer | Zenodo's own revision counter for the record, so an edited record is detectable between runs. | 4 |

#### Dataset views

The dataset ships with named views, so the Output tab, the API and any agent reading this Actor get the columns that matter for a job without asking for all of them.

**Overview** (`?view=overview`) - 7 columns: `doi`, `title`, `publicationDate`, `resourceType`, `license`, `accessRight`, `url`

| Column | Header |
| --- | --- |
| `doi` | DOI |
| `title` | Title |
| `publicationDate` | Published |
| `resourceType` | Type |
| `license` | License |
| `accessRight` | Access |
| `url` | Zenodo Page |

**Authors, Communities & Funding** (`?view=authors`) - 7 columns: `doi`, `title`, `creators`, `communities`, `funders`, `grantIds`, `keywords`

| Column | Header |
| --- | --- |
| `doi` | DOI |
| `title` | Title |
| `creators` | Creators |
| `communities` | Communities |
| `funders` | Funders |
| `grantIds` | Grant Numbers |
| `keywords` | Keywords |

**Files & Usage** (`?view=files`) - 7 columns: `doi`, `title`, `fileCount`, `files`, `downloads`, `views`, `updated`

| Column | Header |
| --- | --- |
| `doi` | DOI |
| `title` | Title |
| `fileCount` | Files |
| `files` | File List |
| `downloads` | Downloads |
| `views` | Views |
| `updated` | Last Updated |

Fetch one directly:

```
GET https://api.apify.com/v2/datasets/{datasetId}/items?view=overview&format=csv
```

#### One real row

Unedited, from this Actor's own most recent successful run.

```json
{
  "doi": "10.5281/zenodo.19983311",
  "paperId": "10.5281/zenodo.19983311",
  "recordId": "19983311",
  "conceptDoi": "10.5281/zenodo.19983308",
  "conceptRecId": "19983308",
  "title": "A Recurrent Latent Variable Model for Sequential Data",
  "description": "<p>In this paper, we explore the inclusion of latent random variables into the hidden state of a recurrent neural network (RNN) by combining the elements of the variational autoencoder. We argue that through the use of high-level latent random variables, the variational RNN (VRNN) can model the kind of variability observed in highly structured sequential data such as natural speech. We empirically evaluate the proposed model against other related sequential models on four speech datasets and one handwriting dataset. Our results show the important roles that latent random variables can play in the RNN dynamics.</p>",
  "publicationDate": "2026-05-02",
  "resourceType": "Conference proceeding",
  "resourceTypeId": "publication",
  "creators": [
    "Chung, Junyoung",
    "Kastner, Kyle",
    "Dinh, Laurent",
    "Goel, Kratarth",
    "Courville, Aaron",
    "Bengio, Yoshua"
  ],
  "authors": [
    "Chung, Junyoung",
    "Kastner, Kyle",
    "Dinh, Laurent",
    "Goel, Kratarth",
    "Courville, Aaron",
    "Bengio, Yoshua"
  ],
  "license": "cc-by-4.0",
  "accessRight": "open",
  "communities": [],
  "keywords": [],
  "grantIds": [],
  "grantTitles": [],
  "funders": [],
  "revision": 4,
  "files": [
    {
      "filename": "A Recurrent Latent Variable Model.pdf",
      "size": 920496,
      "checksum": "md5:34de781099f964c019089171d73034ce",
      "url": "https://zenodo.org/api/records/19983311/files/A%20Recurrent%20Latent%20Variable%20Model.pdf/content"
    }
  ],
  "fileCount": 1,
  "downloads": 10,
  "views": 28,
  "doiUrl": "https://doi.org/10.5281/zenodo.19983311",
  "url": "https://zenodo.org/records/19983311",
  "created": "2026-05-02T16:50:37.085659+00:00",
  "updated": "2026-05-02T16:50:37.635936+00:00"
}
```

### 🧪 Worked examples

Every block below is an input this Actor has actually run, with the row count of the run that produced it. Copy one straight into the Console.

#### PDFs only, a publication-date window, most read first

```json
{
  "query": "deep learning",
  "file_type": "pdf",
  "date_from": "2025-01-01",
  "date_to": "2026-08-01",
  "sort": "mostviewed",
  "max_records": 25
}
```

That run returned **25 rows** in 8s.

#### Everything one author has deposited

```json
{
  "creator": "Bengio",
  "max_records": 25
}
```

That run returned **25 rows** in 10s. The status message reports the size of the whole match set as well, so you always know what you did not collect: *25 Zenodo records of 39 matching*.

#### Peer-Reviewed Journal Articles

Filter and extract peer-reviewed journal articles and scholarly publications on Zenodo.

```json
{
  "resource_type": "publication-article",
  "max_records": 50
}
```

That run returned **50 rows**.

#### Zenodo General Community Records

Harvest general research publications and open datasets deposited directly into Zenodo.

```json
{
  "query": "zenodo",
  "max_records": 50
}
```

That run returned **50 rows**.

#### 2026 Newly Published Records

Scrape brand new research records, publications, and datasets uploaded in 2026.

```json
{
  "year": "2026",
  "max_records": 50
}
```

That run returned **50 rows**.

#### Creative Commons Attribution 4.0 (CC-BY 4.0)

Harvest research records shared under the liberal CC-BY 4.0 open access license.

```json
{
  "license": "cc-by-4.0",
  "max_records": 50
}
```

That run returned **50 rows**.

### How it works

#### The endpoint it calls

Plain HTTP against the source's own public API. No headless browser, no login, no key of yours:

```
https://zenodo.org/api/records
https://zenodo.org/records/<record_id>
```

One endpoint, the same public search API that serves zenodo.org itself, so every record you get back has a resolvable DOI on the day it was read.

#### The paging it does

The run starts by asking Zenodo for the size of the match set with a single `size=1` request, which is why the status message can tell you *287 records of 41,930 matching* rather than just how many it wrote. It then walks pages of at most 25 - Zenodo's own page ceiling - until your **Max Records** is reached.

Filters are compiled into one Lucene query against the same `q` parameter: resource type becomes `metadata.resource_type.id:`, access status becomes `access_right:`, licence becomes `metadata.rights.id:`, year becomes `publication_date:` and accepts a range such as `[2020 TO 2024]`. Community is a separate `communities` parameter, not part of `q`, so a community filter composes with any of the others.

#### The rate it holds

Every request is retried up to **6 times**, with the wait doubling between attempts, and HTTP **429, 500, 502, 503, 504** are treated as retryable rather than fatal. Each request times out at **30s**.

On top of that there is a deliberate half-second pause between pages. Zenodo publishes a 60 requests/minute limit for unauthenticated callers and this sits well inside it, which is why long runs finish instead of being cut off part-way.

#### What you are charged

One `record-returned` event per row written to the dataset, $0.002 at list price. Charged once per complete Zenodo record written to the default dataset. A rate-limited or unreachable API, a rejected query and a search that matches nothing are never charged.

| Apify plan tier | Price per row |
| --- | --- |
| Free | $0.002 |
| Bronze | $0.0018 |
| Silver | $0.0015 |
| Gold | $0.0013 |
| Platinum | $0.001 |
| Diamond | $0.0008 |

Apify platform usage is absorbed, not passed on, so the row price is the whole bill.

#### Why the dataset is correct at every moment

Rows are flushed to the dataset in batches of 50 as they are built and charged one at a time before the batch is written, so a run that hits its budget cap, a Zenodo error or the run's time limit still leaves you every complete row it had. The status message always names how many of the matching records you actually got.

### ⚠️ Error reference

Derived from this Actor's own exception classes and the HTTP branches in its client. The run always reports what happened in its **status message**, so you never need the log to know what you got.

| Raised | When | What the run does |
| --- | --- | --- |
| `ZenodoError` | Base exception for Zenodo API errors. | Run fails with a status message naming the source and the cause. Nothing further is charged. |
| `SourceBlocked` | Raised when Zenodo API returns persistent 429/5xx or block. | Run fails with: *Zenodo API was unreachable: <exc> Nothing was charged.* |
| `BadQuery` | Raised when Zenodo API rejects a query parameter. | Run fails with: *Zenodo rejected this query: <exc> Check input parameters. Nothing was charged.* |

#### HTTP statuses the client actually handles

| Status | Handling |
| --- | --- |
| `429` | Retried with backoff. The run continues if a later attempt succeeds. |
| `500` | Retried with backoff. The run continues if a later attempt succeeds. |
| `502` | Retried with backoff. The run continues if a later attempt succeeds. |
| `503` | Retried with backoff. The run continues if a later attempt succeeds. |
| `504` | Retried with backoff. The run continues if a later attempt succeeds. |
| `400` | The source rejected the query itself, so retrying it cannot help; the input is at fault. |

#### What each outcome costs

| Outcome | Rows | Charged |
| --- | --- | --- |
| Run finished | Everything the source returned for your filters | One `record-returned` per row |
| Nothing matched your filters | 0 | **Nothing** |
| Source refused or was unreachable | Whatever was already collected | Only the rows you got |
| Budget cap reached mid-run | The rows collected before the cap | Only those rows |

Status messages this Actor can end on, verbatim from its own source:

- Run budget of $ cannot cover a single record at $<record event price usd> plus the $<start cost> start event. Raise to at least $\<start cost + record event price usd>. Nothing was charged.
- Zenodo rejected this query: <exc> Check input parameters. Nothing was charged.
- Zenodo API was unreachable: <exc> Nothing was charged.
- No Zenodo records match this query, so nothing was charged. Widen search terms or loosen filters.
- Harvested <pushed> Zenodo records so far…

### ❓ FAQ

##### How do I search for open access datasets only?

Set `resource_type` to `dataset` and `access_right` to `open`.

##### How are DOIs formatted?

Every record includes a standard `doi` (e.g. `10.5281/zenodo.21778617`) and a canonical `paperId` where colons are replaced by hyphens (`10.5281-zenodo.21778617`).

##### What happens if Zenodo rate limits the request?

The scraper automatically detects HTTP 429 status codes and pauses execution using exponential backoff before retrying, ensuring no data loss.

***

#### Do I need an API key, an account or a proxy?

No. The Actor reads the source's public interface over plain HTTP, with no credential of yours anywhere in the run and no headless browser. You need an Apify account and nothing else.

#### What does a run cost?

One `record-returned` event per row written, $0.002 at list price, falling to $0.0008 on the highest Apify plan tier. Apify platform usage is absorbed rather than passed on, so the row price is the whole bill. A run that matches nothing is charged nothing.

#### Can I get just some of the columns?

Yes. The dataset ships named views (`overview`, `authors`, `files`); add `?view=<name>` to the dataset items URL, or pick one in the Output tab, and you get that view's columns in JSON, CSV or Excel.

#### Can an agent call this?

Yes, through Apify's MCP server at <https://mcp.apify.com> with nothing to integrate on our side: `search-actors` finds it, `fetch-actor-details` returns the input schema above, and `call-actor` runs it.

#### Will the dataset still be there next month?

Yes. The run names its own default dataset at startup, and Apify keeps named storages past the retention window that deletes unnamed ones, so the run id in a row you cited a year ago still resolves.

#### Is the data complete?

It is exactly what the source returned for your filters. Nothing is inferred, modelled or filled in, and a field the source did not return is empty rather than guessed. The run's status message tells you what you got, so you never have to read the log to find out.

### 💬 Your feedback

Have suggestions, feature requests, or custom extraction needs? Contact us at `reapxdev@proton.me`.

***

*Disclaimer: Unofficial - not affiliated with Zenodo or CERN. Collects public data only. reapx. Contact reapxdev@proton.me.*

# Actor input Schema

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

Full-text search query across titles, abstracts, subjects, and authors (e.g. <code>climate change</code>, <code>machine learning</code>). Leave empty to query all Zenodo records. The broader the query, the more total records matched.

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

Filter records belonging to a specific Zenodo community slug (e.g. <code>biosyslit</code>, <code>eu</code>, <code>ecfunded</code>). Leave this field empty to search across all communities nationwide and worldwide.

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

Filter by resource type identifier. Select a specific publication type, dataset, software, or image category. Leave empty to retrieve records of all resource types without restrictions.<br><br>Options: <code></code>, <code>publication-article</code>, <code>publication-preprint</code>, <code>publication-book</code>, <code>publication-conferencepaper</code>, <code>publication-report</code>.

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

Filter by record access status. Choose <code>open</code> for open-access publications, or filter restricted/embargoed records. Leave empty to return records of any access level.

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

Filter records by license identifier (e.g. <code>cc-by-4.0</code>, <code>mit</code>, <code>cc0-1.0</code>, <code>apache-2.0</code>). Leave empty to return research records regardless of license.

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

Return only records with this person or organisation among the creators (e.g. <code>Hansen</code>, <code>European Commission</code>). Matched against Zenodo's own <code>metadata.creators.person\_or\_org.name</code> field.<br><br><b>Empty value:</b> leave empty to include records by any author.<br><br><b>Consequence:</b> the match is on the name string Zenodo holds, so a common surname still returns many people; combine it with a community or a year to narrow it.

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

Return only records that carry at least one file of this type (e.g. <code>pdf</code>, <code>csv</code>, <code>zip</code>, <code>xlsx</code>). Sent as Zenodo's own <code>file\_type</code> facet, not as free text.<br><br><b>Empty value:</b> leave empty to include records whatever they carry, including metadata-only records with no files at all.<br><br><b>Consequence:</b> filtering to <code>pdf</code> is the quickest way to drop metadata-only and embargoed records from a corpus you intend to read.

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

Filter by publication year (e.g. <code>2024</code>) or Lucene year range (e.g. <code>\[2020 TO 2024]</code>). Leave empty to include records published in any year.

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

Only records published on or after this date (e.g. <code>2024-01-01</code>). Compiled into a Zenodo <code>publication\_date</code> range together with Published To.<br><br><b>Empty value:</b> leave empty for no lower bound.<br><br><b>Consequence:</b> a tight window is the cheapest way to make a repeat run return only what is new since the last one.

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

Only records published on or before this date (e.g. <code>2024-12-31</code>).<br><br><b>Empty value:</b> leave empty for no upper bound, which is what you want for a monitoring run.<br><br><b>Consequence:</b> pairs with Published From; either bound works on its own, and both are applied by Zenodo rather than after the fact.

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

Order Zenodo returns matches in: <code>newest</code> for the latest uploads, <code>bestmatch</code> for relevance to your query, <code>mostrecent</code> for the most recently updated, <code>mostviewed</code> for the most read.<br><br><b>Empty value:</b> falls back to <code>newest</code>.<br><br><b>Consequence:</b> ordering decides which records you get when Max Records is smaller than the match count, which it usually is; it does not change what a row costs.

## `all_versions` (type: `boolean`):

Return every version of a record, not just the latest one. Zenodo mints a DOI per version and a concept DOI for the record as a whole, and both are on every row.<br><br><b>Turned off (the default):</b> one row per record, the current version.<br><br><b>Consequence:</b> this genuinely multiplies rows and therefore cost - the same query measured 218,626 matches with it off and 260,002 with it on - so raise Max Records deliberately, not by accident.

## `max_records` (type: `integer`):

Maximum number of Zenodo research records to return and write to the dataset (1 to 10,000). The higher the number, the longer the run takes and the more it costs.<br><br>The higher the number, the longer the run takes and the more you are charged, because you pay per row returned.

## Actor input object example

```json
{
  "query": "climate change",
  "sort": "newest",
  "max_records": 50
}
```

# Actor output Schema

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

Every research record found by this run, one row per item, in the default dataset.

# 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 = {
    "query": "climate change",
    "community": "",
    "resource_type": "",
    "access_right": "",
    "license": "",
    "creator": "",
    "file_type": "",
    "year": "",
    "date_from": "",
    "date_to": "",
    "sort": "newest",
    "all_versions": false,
    "max_records": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("reapx/zenodo-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 = {
    "query": "climate change",
    "community": "",
    "resource_type": "",
    "access_right": "",
    "license": "",
    "creator": "",
    "file_type": "",
    "year": "",
    "date_from": "",
    "date_to": "",
    "sort": "newest",
    "all_versions": False,
    "max_records": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("reapx/zenodo-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 '{
  "query": "climate change",
  "community": "",
  "resource_type": "",
  "access_right": "",
  "license": "",
  "creator": "",
  "file_type": "",
  "year": "",
  "date_from": "",
  "date_to": "",
  "sort": "newest",
  "all_versions": false,
  "max_records": 50
}' |
apify call reapx/zenodo-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,reapx/zenodo-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/8aKQnRSbhnqcXVH8h/builds/qbC4RNynRuc9372nm/openapi.json
