Skip to content

Research Products

ResearchProductsFilters

Bases: BaseModel

Filter model for the Research Products V3 endpoint (/v3/research-products).

Field names mirror the V3 query parameters exactly. See the V3 OpenAPI spec for the authoritative list and allowed values. Most string fields support inline logical operators (OR/AND/NOT) with double-quoted values.

Notable V3 changes from V1/V2: - authorOrcid renamed to authorId. - bestOpenAccessRightLabel renamed to accessRightLabel (allowed: Open Access, Closed Access, Restricted, Open Source, Embargo, Unknown). - sdg (was list[str]) renamed to sdgLabel (str; use inline OR for multiple, e.g. sdgLabel='"3. Good health" OR "4. Education"'). - 17 new fields added (publication-year filters, language, hasLicense, relProject, relFunder, relFundingLevel{0,1,2}Id, source, eoscIfGuidelines, subCommunity, relCommunityName, relOrganization, relHostingDataSource, excludePubDateRange).

Source code in src/aireloom/endpoints.py
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
class ResearchProductsFilters(BaseModel):
    """Filter model for the Research Products V3 endpoint (``/v3/research-products``).

    Field names mirror the V3 query parameters exactly. See the V3 OpenAPI spec
    for the authoritative list and allowed values. Most string fields support
    inline logical operators (``OR``/``AND``/``NOT``) with double-quoted values.

    Notable V3 changes from V1/V2:
        - ``authorOrcid`` renamed to ``authorId``.
        - ``bestOpenAccessRightLabel`` renamed to ``accessRightLabel``
          (allowed: Open Access, Closed Access, Restricted, Open Source, Embargo,
          Unknown).
        - ``sdg`` (was ``list[str]``) renamed to ``sdgLabel`` (``str``; use inline
          OR for multiple, e.g. ``sdgLabel='"3. Good health" OR "4. Education"'``).
        - 17 new fields added (publication-year filters, ``language``, ``hasLicense``,
          ``relProject``, ``relFunder``, ``relFundingLevel{0,1,2}Id``, ``source``,
          ``eoscIfGuidelines``, ``subCommunity``, ``relCommunityName``,
          ``relOrganization``, ``relHostingDataSource``, ``excludePubDateRange``).
    """

    search: str | None = None
    mainTitle: str | None = None
    description: str | None = None
    id: str | None = None
    pid: str | None = None
    originalId: str | None = None
    type: Literal["publication", "dataset", "software", "other"] | None = None
    fromPublicationDate: date | None = None
    toPublicationDate: date | None = None
    fromPublicationYear: int | None = None
    toPublicationYear: int | None = None
    publicationYear: str | None = None
    excludePubDateRange: bool | None = None
    subjects: list[str] | None = None
    language: str | None = None
    countryCode: str | None = None
    authorFullName: str | None = None
    authorId: str | None = None
    publisher: str | None = None
    accessRightLabel: str | None = None
    influenceClass: str | None = None
    impulseClass: str | None = None
    popularityClass: str | None = None
    citationCountClass: str | None = None
    instanceType: str | None = None
    sdgLabel: str | None = None
    fos: list[str] | None = None
    isPeerReviewed: bool | None = None
    isInDiamondJournal: bool | None = None
    isPubliclyFunded: bool | None = None
    hasLicense: bool | None = None
    isGreen: bool | None = None
    openAccessColor: str | None = None
    eoscIfGuidelines: str | None = None
    relOrganizationId: str | None = None
    relOrganization: str | None = None
    relCommunityId: str | None = None
    relCommunityName: str | None = None
    subCommunity: str | None = None
    relProjectId: str | None = None
    relProjectCode: str | None = None
    relProject: str | None = None
    hasProjectRel: bool | None = None
    relProjectFundingShortName: str | None = None
    relProjectFundingStreamId: str | None = None
    relFunder: str | None = None
    relFundingLevel0Id: str | None = None
    relFundingLevel1Id: str | None = None
    relFundingLevel2Id: str | None = None
    relHostingDataSourceId: str | None = None
    relHostingDataSource: str | None = None
    relCollectedFromDatasourceId: str | None = None
    source: str | None = None
    rorId: str | None = None
    logicalOperator: Literal["AND", "OR", "NOT"] | None = None

    model_config = ConfigDict(extra="forbid")

accessRightLabel = None class-attribute instance-attribute

authorFullName = None class-attribute instance-attribute

authorId = None class-attribute instance-attribute

citationCountClass = None class-attribute instance-attribute

countryCode = None class-attribute instance-attribute

description = None class-attribute instance-attribute

eoscIfGuidelines = None class-attribute instance-attribute

excludePubDateRange = None class-attribute instance-attribute

fos = None class-attribute instance-attribute

fromPublicationDate = None class-attribute instance-attribute

fromPublicationYear = None class-attribute instance-attribute

hasLicense = None class-attribute instance-attribute

hasProjectRel = None class-attribute instance-attribute

id = None class-attribute instance-attribute

impulseClass = None class-attribute instance-attribute

influenceClass = None class-attribute instance-attribute

instanceType = None class-attribute instance-attribute

isGreen = None class-attribute instance-attribute

isInDiamondJournal = None class-attribute instance-attribute

isPeerReviewed = None class-attribute instance-attribute

isPubliclyFunded = None class-attribute instance-attribute

language = None class-attribute instance-attribute

logicalOperator = None class-attribute instance-attribute

mainTitle = None class-attribute instance-attribute

model_config = ConfigDict(extra='forbid') class-attribute instance-attribute

openAccessColor = None class-attribute instance-attribute

originalId = None class-attribute instance-attribute

pid = None class-attribute instance-attribute

popularityClass = None class-attribute instance-attribute

publicationYear = None class-attribute instance-attribute

publisher = None class-attribute instance-attribute

relCollectedFromDatasourceId = None class-attribute instance-attribute

relCommunityId = None class-attribute instance-attribute

relCommunityName = None class-attribute instance-attribute

relFunder = None class-attribute instance-attribute

relFundingLevel0Id = None class-attribute instance-attribute

relFundingLevel1Id = None class-attribute instance-attribute

relFundingLevel2Id = None class-attribute instance-attribute

relHostingDataSource = None class-attribute instance-attribute

relHostingDataSourceId = None class-attribute instance-attribute

relOrganization = None class-attribute instance-attribute

relOrganizationId = None class-attribute instance-attribute

relProject = None class-attribute instance-attribute

relProjectCode = None class-attribute instance-attribute

relProjectFundingShortName = None class-attribute instance-attribute

relProjectFundingStreamId = None class-attribute instance-attribute

relProjectId = None class-attribute instance-attribute

rorId = None class-attribute instance-attribute

sdgLabel = None class-attribute instance-attribute

search = None class-attribute instance-attribute

source = None class-attribute instance-attribute

subCommunity = None class-attribute instance-attribute

subjects = None class-attribute instance-attribute

toPublicationDate = None class-attribute instance-attribute

toPublicationYear = None class-attribute instance-attribute

type = None class-attribute instance-attribute

ResearchProductsClient

Bases: GraphV3FilterSerializationMixin, BatchMixin, GettableMixin, SearchableMixin, CursorIterableMixin, BaseResourceClient

Client for the OpenAIRE Research Products API endpoint.

This client provides standardized methods (get, search, iterate) for accessing research product data, by inheriting from bibliofabric mixins. It also provides search_links, iterate_links, and get_relations_info for the V3 /research-products/links sub-endpoint.

Attributes:

Name Type Description
_entity_path str

The API path for research products.

_entity_model type[ResearchProduct]

Pydantic model for a single research product.

_search_response_model type[ResearchProductResponse]

Pydantic model for the search response envelope.

Source code in src/aireloom/resources/research_products_client.py
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
class ResearchProductsClient(
    GraphV3FilterSerializationMixin,
    BatchMixin,
    GettableMixin,
    SearchableMixin,
    CursorIterableMixin,
    BaseResourceClient,
):
    """Client for the OpenAIRE Research Products API endpoint.

    This client provides standardized methods (`get`, `search`, `iterate`) for
    accessing research product data, by inheriting from `bibliofabric` mixins.
    It also provides `search_links`, `iterate_links`, and `get_relations_info`
    for the V3 ``/research-products/links`` sub-endpoint.

    Attributes:
        _entity_path (str): The API path for research products.
        _entity_model (type[ResearchProduct]): Pydantic model for a single research product.
        _search_response_model (type[ResearchProductResponse]): Pydantic model for the
                                                                search response envelope.
    """

    _entity_path: str = RESEARCH_PRODUCTS
    _entity_model: type[ResearchProduct] = ResearchProduct
    _search_response_model: type[ResearchProductResponse] = ResearchProductResponse
    _batch_fields: dict[str, str] = {
        "doi": "pid",
        "openaire_id": "id",
        "original_id": "originalId",
    }

    def __init__(self, api_client: "AireloomClient"):
        """Initializes the ResearchProductsClient.

        Args:
            api_client: An instance of AireloomClient.
        """
        super().__init__(api_client)
        logger.debug(
            f"ResearchProductsClient initialized for path: {self._entity_path}"
        )

    # Mixin-provided methods: get, search, iterate

    # ------------------------------------------------------------------
    # Links sub-endpoint (V3, 0-indexed page-based pagination)
    # ------------------------------------------------------------------

    async def search_links(
        self,
        *,
        filters: LinksFilters | None = None,
        page: int = 0,
        page_size: int = 20,
    ) -> LinksResponse:
        """Search for relation links between research products.

        Uses the V3 ``/research-products/links`` endpoint (NOT the Scholix API).

        Args:
            filters: Optional :class:`LinksFilters` with filter criteria.
            page: 0-indexed page number.
            page_size: Number of results per page (max 99; values >=100 are silently
                truncated to 10 by V3).

        Returns:
            A :class:`LinksResponse` containing the matching relations.
        """
        if page_size > MAX_LINK_PAGE_SIZE:
            logger.warning(
                "page_size=%d exceeds the V3 links maximum of %d "
                "(values >=100 are silently truncated to 10); clamping.",
                page_size,
                MAX_LINK_PAGE_SIZE,
            )
            page_size = MAX_LINK_PAGE_SIZE
        params: dict[str, Any] = {"page": page, "pageSize": page_size}
        if filters is not None:
            params.update(self._serialize_filters(filters))

        response = await self._api_client.request(
            method="GET",
            path=LINKS,
            params=params,
        )
        return LinksResponse.model_validate(response.json())

    async def iterate_links(
        self,
        *,
        filters: LinksFilters | None = None,
        page_size: int = MAX_LINK_PAGE_SIZE,
    ) -> AsyncIterator[Relation]:
        """Iterate through all relation links matching *filters*.

        Automatically handles page-based pagination using ``totalPages``
        from the response header.

        Args:
            filters: Optional :class:`LinksFilters` with filter criteria.
            page_size: Number of results per page (max 99; see :meth:`search_links`).

        Yields:
            :class:`Relation` objects.
        """
        current_page = 0
        total_pages = 1

        while current_page < total_pages:
            response = await self.search_links(
                filters=filters, page=current_page, page_size=page_size
            )

            if not response.results:
                break

            for rel in response.results:
                yield rel

            if current_page == 0 and response.header is not None:
                total_pages = response.header.totalPages or 1
                if total_pages == 0:
                    break

            if current_page >= total_pages:
                break

            current_page += 1

    async def get_relations_info(self) -> list[dict[str, Any]]:
        """Retrieve available relation types from the links endpoint.

        Uses the V3 ``/research-products/links/relations-info`` endpoint.

        Returns:
            A list of dicts describing relation types (name, inverse, description).
        """
        response = await self._api_client.request(
            method="GET",
            path=f"{LINKS}/relations-info",
            params={},
        )
        data = response.json()
        if isinstance(data, list):
            return data
        return [data]

Search for relation links between research products.

Uses the V3 /research-products/links endpoint (NOT the Scholix API).

Parameters:

Name Type Description Default
filters LinksFilters | None

Optional :class:LinksFilters with filter criteria.

None
page int

0-indexed page number.

0
page_size int

Number of results per page (max 99; values >=100 are silently truncated to 10 by V3).

20

Returns:

Name Type Description
A LinksResponse

class:LinksResponse containing the matching relations.

Source code in src/aireloom/resources/research_products_client.py
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
async def search_links(
    self,
    *,
    filters: LinksFilters | None = None,
    page: int = 0,
    page_size: int = 20,
) -> LinksResponse:
    """Search for relation links between research products.

    Uses the V3 ``/research-products/links`` endpoint (NOT the Scholix API).

    Args:
        filters: Optional :class:`LinksFilters` with filter criteria.
        page: 0-indexed page number.
        page_size: Number of results per page (max 99; values >=100 are silently
            truncated to 10 by V3).

    Returns:
        A :class:`LinksResponse` containing the matching relations.
    """
    if page_size > MAX_LINK_PAGE_SIZE:
        logger.warning(
            "page_size=%d exceeds the V3 links maximum of %d "
            "(values >=100 are silently truncated to 10); clamping.",
            page_size,
            MAX_LINK_PAGE_SIZE,
        )
        page_size = MAX_LINK_PAGE_SIZE
    params: dict[str, Any] = {"page": page, "pageSize": page_size}
    if filters is not None:
        params.update(self._serialize_filters(filters))

    response = await self._api_client.request(
        method="GET",
        path=LINKS,
        params=params,
    )
    return LinksResponse.model_validate(response.json())

Iterate through all relation links matching filters.

Automatically handles page-based pagination using totalPages from the response header.

Parameters:

Name Type Description Default
filters LinksFilters | None

Optional :class:LinksFilters with filter criteria.

None
page_size int

Number of results per page (max 99; see :meth:search_links).

MAX_LINK_PAGE_SIZE

Yields:

Type Description
AsyncIterator[Relation]

class:Relation objects.

Source code in src/aireloom/resources/research_products_client.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
async def iterate_links(
    self,
    *,
    filters: LinksFilters | None = None,
    page_size: int = MAX_LINK_PAGE_SIZE,
) -> AsyncIterator[Relation]:
    """Iterate through all relation links matching *filters*.

    Automatically handles page-based pagination using ``totalPages``
    from the response header.

    Args:
        filters: Optional :class:`LinksFilters` with filter criteria.
        page_size: Number of results per page (max 99; see :meth:`search_links`).

    Yields:
        :class:`Relation` objects.
    """
    current_page = 0
    total_pages = 1

    while current_page < total_pages:
        response = await self.search_links(
            filters=filters, page=current_page, page_size=page_size
        )

        if not response.results:
            break

        for rel in response.results:
            yield rel

        if current_page == 0 and response.header is not None:
            total_pages = response.header.totalPages or 1
            if total_pages == 0:
                break

        if current_page >= total_pages:
            break

        current_page += 1

get_relations_info() async

Retrieve available relation types from the links endpoint.

Uses the V3 /research-products/links/relations-info endpoint.

Returns:

Type Description
list[dict[str, Any]]

A list of dicts describing relation types (name, inverse, description).

Source code in src/aireloom/resources/research_products_client.py
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
async def get_relations_info(self) -> list[dict[str, Any]]:
    """Retrieve available relation types from the links endpoint.

    Uses the V3 ``/research-products/links/relations-info`` endpoint.

    Returns:
        A list of dicts describing relation types (name, inverse, description).
    """
    response = await self._api_client.request(
        method="GET",
        path=f"{LINKS}/relations-info",
        params={},
    )
    data = response.json()
    if isinstance(data, list):
        return data
    return [data]