AIREloom: Asynchronous Python client for the OpenAIRE API
Samuel Mok · s.mok@utwente.nl · 2025–2026
AIREloom is an async Python client for the OpenAIRE Graph API and Scholexplorer API, built on bibliofabric.
Docs: utsmok.github.io/AIREloom · PyPI: aireloom · License: MIT
Features
- Full API coverage — Research Products (v3), Projects, Organizations, Data Sources, Persons, Research Product Links, and Scholexplorer (v1/v2/v3)
- Async by design — built on
httpx+asynciowith proper connection pooling - Typed throughout — Pydantic models for all inputs/outputs, PEP 561
py.typedmarker - Ergonomics layer — computed properties, SafeStr/SafeList defaults, convenience queries, iterator helpers
- Flexible auth — auto-detection from env vars, static tokens, OAuth2 client credentials, or no auth
- Resilient — retries with backoff, rate-limit handling (
Retry-After), optional client-side caching, hook system
Installation
uv add aireloom
Or with pip: pip install aireloom. Requires Python ≥3.12.
Quick Start
import asyncio
from aireloom import AireloomSession
from aireloom.endpoints import ResearchProductsFilters
async def main():
async with AireloomSession() as session:
# Search publications
filters = ResearchProductsFilters(
type="article", mainTitle="climate change",
fromPublicationDate="2024-01-01",
)
response = await session.research_products.search(
filters=filters, page_size=5, sortBy="publicationDate desc",
)
for product in response.results:
print(f"{product.title} — DOI: {product.doi or 'N/A'}")
# Iterate all results (cursor-based auto-pagination)
filters2 = ResearchProductsFilters(type="dataset", countryCode="NL")
async for product in session.research_products.iterate(
filters=filters2, page_size=50,
):
print(product.title)
break # stop when you want
# Get a single entity
product = await session.research_products.get("doi:10.1038/s41586-021-03964-9")
print(product.title, product.doi)
# Convenience queries
citations = await session.queries.citing_works("doi:10.1038/s41586-021-03964-9")
print(f"{len(citations)} citations")
asyncio.run(main())
No authentication required — the OpenAIRE API works without it. For higher rate limits, see Authentication.
Core API
Retrieval methods
Every resource client (session.research_products, session.organizations, etc.) provides:
| Method | Description |
|---|---|
get(id) |
Retrieve a single entity by ID |
search(filters, page, page_size, sortBy) |
Paginated search |
iterate(filters, page_size, sortBy) |
Auto-paginate all results (cursor-based) |
collect(limit, ...) |
Collect results into a list |
count(filters) |
Count matching entities |
first(filters) |
Get first matching entity or None |
Resource clients
| Client | API | Notes |
|---|---|---|
session.research_products |
Graph API v2 | Enriched responses with related entities |
session.projects |
Graph API v1 | |
session.organizations |
Graph API v1 | |
session.data_sources |
Graph API v1 | |
session.persons |
Graph API v1 | givenName/lastName filters cause 500s (upstream bug) |
session.scholix |
Scholix v3 | search_links() / iterate_links() |
Ergonomics
Computed properties on models — derived fields that don't exist in the raw API response:
product.doi # → "10.1234/example" (extracted from pids)
product.publication_year # → 2024 (from publicationDate)
product.is_open_access # → True (from bestAccessRight)
org.ror_id # → "https://ror.org/..." (from rorId)
project.funder_name # → "EC" (from funding array)
SafeStr/SafeList — None is never returned for string/list fields; you get "" or [] instead:
product.subjects # [] instead of None
product.title # "" instead of None
Convenience queries via session.queries:
session.queries.publications_by_doi("10.1234/example")
session.queries.citing_works("doi:10.1234/example")
session.queries.datasets_by_organization("openaire____::orgID:grid.5522.e")
session.queries.projects_by_funder("EC")
See Ergonomics docs for the full list.
Filters & Sorting
Filters are Pydantic models — import from aireloom.endpoints:
from aireloom.endpoints import ResearchProductsFilters, ProjectsFilters
filters = ResearchProductsFilters(
type="article",
mainTitle="machine learning",
fromPublicationDate="2024-01-01",
countryCode="NL",
)
Sort with sortBy="field asc" or sortBy="field desc". Valid fields depend on the endpoint (see ENDPOINT_DEFINITIONS).
Authentication
AIREloom auto-detects auth from environment variables or .env files (prefixed with AIRELOOM_). No auth is the default if nothing is configured.
# Option 1: Static token
AIRELOOM_OPENAIRE_API_TOKEN=your_token
# Option 2: OAuth2 client credentials
AIRELOOM_OPENAIRE_CLIENT_ID=your_id
AIRELOOM_OPENAIRE_CLIENT_SECRET=your_secret
Or pass explicitly:
from bibliofabric.auth import NoAuth, StaticTokenAuth, ClientCredentialsAuth
session = AireloomSession(auth_strategy=StaticTokenAuth(token="..."))
See Authentication docs for details.
Error Handling
from bibliofabric.exceptions import (
BibliofabricError, APIError, NotFoundError, RateLimitError,
TimeoutError, NetworkError, AuthError, ValidationError,
)
See Error Handling docs for the full hierarchy.
Examples
All examples in examples/ are dual-purpose — run as scripts or as interactive marimo notebooks:
# As a script
uv run examples/simple_example.py
# As an interactive notebook
uv run marimo edit examples/simple_example.py
| Script | Description |
|---|---|
simple_example.py |
Search, iterate, get research products |
02_scholix_link_discovery.py |
Discover publication–dataset links via Scholexplorer |
03_research_product_analysis.py |
Deep-dive into product metadata |
04_organization_projects.py |
Organizations and their projects |
05_advanced_filtering.py |
Complex filter combinations |
06_persons_discovery.py |
Person search and co-authorship |
07_ergonomics_showcase.py |
Before/after: raw API vs ergonomics layer |
08_iterator_helpers.py |
collect(), count(), first() |
09_computed_fields_and_safe_types.py |
Computed properties and SafeStr/SafeList |
10_convenience_queries.py |
All convenience query functions |
See examples/README.md for marimo embedding and WASM details.
Known OpenAIRE API Issues
Full bug report with reproduction steps: OPENAIRE_BUG_REPORT.md.
- Persons
givenName/lastNamefilters — cause HTTP 500 despite being in the spec pageSize=100on Links/Scholix — silently falls back to 10. UsepageSize ≤ 99- Undocumented endpoints — Persons has no filtering docs; Links (
/v1/researchProducts/links) is entirely undocumented - Sort field issues — Persons only accepts
relevance; Data Sources error messages say "organizations"
Development
uv sync # install dev deps
uv run pytest -x -q # run tests
uvx ruff check src/ # lint
uvx ty check src/ # type check
Pre-commit hooks for ruff format + lint are configured in .pre-commit-config.yaml.
Contributions welcome — see Contributing.
Metadata
Release files for aireloom 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| aireloom-0.6.0.tar.gz | 341.5 kB | Without provenance | Details for aireloom-0.6.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| aireloom-0.6.0-py3-none-any.whl | Python 3 | none | any | Details for aireloom-0.6.0-py3-none-any.whl |
Total release size: 400.9 kB
Release files / aireloom-0.6.0.tar.gz
| Download URL | aireloom-0.6.0.tar.gz |
|---|---|
| Size | 341.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
103e912e59f148371f7c39fd79737b2091eeeb6832f147c128ff2e277ede39c6
|
|
BLAKE2b-256 checksum How to use checksums |
a60db269a41d09cc959f5594d48abd72dc109004cf100776ab9dfb0e1c23a926
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / aireloom-0.6.0-py3-none-any.whl
| Download URL | aireloom-0.6.0-py3-none-any.whl |
|---|---|
| Size | 59.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
65b7971c1e14e5d5a29b83905f08a4a4016c8e1a61e57cc18e69d68c20bd7145
|
|
BLAKE2b-256 checksum How to use checksums |
ef405d2d0f6f34906c225c97175941a4f1f84a245e2b4b9bf197c61a0602ad1f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for aireloom 0.6.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.