Zenodo APIzenodo.org ↗
Search and retrieve records, files, versions, communities, and statistics from Zenodo's open science repository via 10 structured endpoints.
What is the Zenodo API?
This API exposes 10 endpoints covering Zenodo's open science repository, letting you search across millions of research records, retrieve full metadata and file listings, inspect version histories, and pull per-record download and view statistics. The search_and_extract_results endpoint returns a normalized flat list — including stripped abstracts, author arrays, DOIs, and direct download links — without any post-processing on your end.
curl -X GET 'https://api.parse.bot/scraper/6d5c7106-d65c-4baf-8bb3-3f87a8a17925/search_records?q=climate+change&page=1&size=5&sort=bestmatch' \ -H 'X-API-Key: $PARSE_API_KEY'
Typed, relational, agent-ready
A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.
- Fully typed · autocompletes
- Objects link to objects
- Typed errors & pagination
Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:
uv add parse-sdk uv run parse init uv run parse add --marketplace zenodo-org-api
uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.
"""Walkthrough: Zenodo SDK — search records, explore communities, drill into files and stats."""
from parse_apis.zenodo_api import Zenodo, RecordSort, AccessRight, CommunitySort, RecordNotFound
zenodo = Zenodo()
# Search open-access records about climate, sorted by most recent
for record in zenodo.records.search(q="climate change", sort=RecordSort.MOST_RECENT, access_right=AccessRight.OPEN, limit=5):
print(record.id, record.title, record.publication_date)
# Drill into one record's files, versions, and stats
record = zenodo.records.search(q="machine learning", limit=1).first()
if record:
for f in record.files.list(limit=5):
print(f.key, f.size, f.mimetype)
for ver in record.versions.list(limit=3):
print(ver.id, ver.created, ver.is_latest)
st = record.stats()
print(st.views, st.downloads, st.data_volume)
# Construct a community by slug and search its records
community = zenodo.community("climate_crices")
for rec in community.search_records(q="adaptation", sort=RecordSort.BEST_MATCH, limit=3):
print(rec.id, rec.title)
# Search communities by keyword
for comm in zenodo.communities.search(q="climate", sort=CommunitySort.NEWEST, limit=3):
print(comm.slug, comm.title)
# Typed error handling: catch a missing record
try:
result = zenodo.records.search(q="nonexistent_xyz_impossible_query_12345", limit=1).first()
if result:
latest = result.latest_version()
print(latest.id, latest.title)
except RecordNotFound as exc:
print(f"Record not found: {exc.record_id}")
# Normalized publication search for simplified metadata
for pub in zenodo.extractedpublications.search(q="deep learning", limit=3):
print(pub.title, pub.doi, pub.authors)
print("exercised: records.search / record.files.list / record.versions.list / record.stats / community.search_records / communities.search / record.latest_version / extractedpublications.search")
Search Zenodo records with various filters and pagination. Returns paginated results with aggregations for faceted filtering. Supports query syntax, resource type/subtype filtering, community membership filtering, and access-right filtering. Results are ordered by sort parameter.
| Param | Type | Description |
|---|---|---|
| qrequired | string | Search query string. |
| page | integer | Page number for pagination. |
| size | integer | Number of results per page. |
| sort | string | Sort order. |
| type | string | Filter by resource type (e.g., 'publication', 'software', 'dataset'). |
| subtype | string | Filter by resource subtype (e.g., 'article', 'preprint'). |
| communities | string | Filter by community slug. |
| access_right | string | Filter by access right. |
| resource_type | string | Filter by resource type (e.g., 'publication', 'dataset'). |
{
"type": "object",
"fields": {
"links": "object with pagination links (self, next)",
"total": "integer total number of matching records",
"sortBy": "string indicating the current sort order",
"results": "array of record objects",
"aggregations": "object with faceted filter buckets"
},
"sample": {
"data": {
"links": {
"self": "https://zenodo.org/api/records?page=1&q=climate+change&size=5&sort=bestmatch"
},
"total": 203523,
"sortBy": "bestmatch",
"results": [
{
"id": "14612905",
"links": {
"self_html": "https://zenodo.org/records/14612905"
},
"metadata": {
"title": "climatechange-ai-tutorials/camels-hydrological-modeling",
"publication_date": "2025-01-08"
}
}
],
"aggregations": {
"access_status": {
"buckets": [
{
"key": "open",
"doc_count": 191569
}
]
}
}
},
"status": "success"
}
}About the Zenodo API
Search and Record Retrieval
The search_records endpoint accepts a free-text q parameter alongside filters for type (e.g., publication, software), subtype, access_right, and communities. Results come back as a paginated hits object with a total count, plus an aggregations object containing facet buckets for publication_date, access_status, resource_type, subject, and file_type — ready for building faceted search UIs. The get_record endpoint retrieves a single record by numeric ID and returns the full metadata object (title, DOI, publication date, creators, description), a files array with checksums and sizes, and a stats object with download and view counts.
Files, Versions, and Statistics
get_record_files returns an entries array per record with each file's key, size, mimetype, checksum, and a links object containing direct download URLs. get_record_versions lists all versions of a record as a paginated hits array, and get_latest_record_version resolves any record ID to its most current version — useful when working with non-canonical version IDs. get_record_stats gives both per-version and all-version aggregate counts: views, downloads, unique_views, unique_downloads, and their version_* counterparts.
Communities
list_communities supports q, sort (bestmatch or newest), and pagination, and returns an aggregations object with type, funder, and organization facets. get_community accepts either a community slug or UUID and returns the community's access settings (visibility, member policy) and metadata (title, curation policy). search_records_in_community scopes a full record search to a specific community, accepting the same q, sort, and pagination parameters as the global search.
Normalized Extraction
search_and_extract_results provides a simplified alternative to search_records: instead of deeply nested metadata objects, it returns a flat list where each item includes doi, url, date, lang, title, source, authors (array of name strings), abstract (HTML stripped), keywords, and download_link. This is the fastest path when you only need publication-level fields without traversing nested structures.
The Zenodo API is a managed, monitored endpoint for zenodo.org — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when zenodo.org changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.
This isn't an official zenodo.org API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.
Will this API break when the source site changes?+
Is this an official API from the source site?+
Can I fix or extend this API myself if I need a new endpoint or field?+
What happens if I call an endpoint that has an issue?+
- Building a literature discovery tool that filters Zenodo records by
resource_type,access_right, and keyword usingsearch_recordsaggregations - Tracking dataset download popularity by polling
get_record_statsforversion_downloadsandunique_downloadsacross a set of record IDs - Resolving the latest DOI for a versioned dataset using
get_latest_record_versionbefore citing or referencing it programmatically - Listing all files attached to a software deposit with
get_record_filesto surfacemimetype,size, and direct download links - Monitoring community growth by paginating
list_communitiesand filtering aggregations byfunderororganization - Extracting structured author and abstract data from open-access publications using
search_and_extract_resultswithout parsing nested metadata - Auditing all versions of a dataset record with
get_record_versionsto detect when new files or updated metadata were published
| Tier | Price | Credits/month | Rate limit |
|---|---|---|---|
| Free | $0/mo | 100 | 5 req/min |
| Hobby | $30/mo | 1,000 | 20 req/min |
| Developer | $100/mo | 5,000 | 100 req/min |
One credit = one API call regardless of which marketplace API you call. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.
Does Zenodo have an official developer API?+
What does `search_and_extract_results` return that `search_records` does not?+
search_and_extract_results returns a flat list with fields like abstract (HTML stripped), authors as a plain string array, download_link as a direct URL, and lang — all pre-extracted so you don't need to traverse the nested metadata and files structures that search_records returns.Does `get_record_stats` include time-series or historical view/download data?+
views, downloads, unique_views, unique_downloads, and their version_* equivalents — but no breakdown by date or time period. You can fork this API on Parse and revise it to add a time-windowed statistics endpoint if Zenodo's stats surface supports it.Can I retrieve records that require authentication or are under restricted access?+
access_right field; you can filter by open, embargoed, restricted, or closed using the access_right parameter in search_records. Metadata for restricted or closed records may be limited or absent. The API currently covers publicly accessible data. You can fork it on Parse and revise to add authenticated access for restricted record content.Is there a way to retrieve all records belonging to a specific community without searching?+
search_records_in_community, which scopes results to a community slug and returns a paginated hits array. It requires a community_id and optionally accepts q, sort, and pagination parameters. A full unfiltered dump of all community records is not a separate endpoint currently. You can fork this API on Parse and revise to add a dedicated listing endpoint.