# ORCID Scraper: Researcher Profiles & Affiliations (`arman-bd/orcid-researcher-scraper`) Actor

Scrape public ORCID records: name, biography, employment and education history, funding, peer reviews and the full works list with DOIs. Search by name or fetch by iD.

- **URL**: https://apify.com/arman-bd/orcid-researcher-scraper.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** Developer tools, AI, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.74 / 1,000 researcher scrapeds

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/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

## ORCID Scraper: Researcher Profiles & Affiliations

![ORCID Scraper: Public researcher records by iD or search, names, affiliations, funding, works and DOIs](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/orcid-researcher-scraper.jpg)

Turns an ORCID iD, or a name search, into a structured researcher record: names and aliases, biography, keywords, employment and education history with ROR-identified organisations, funding awards with grant numbers, peer-review counts and the full works list with DOIs.

ORCID is the scholarly world's identity registry, and its public API is designed to be read by machines. This Actor reads it directly, so there is no browser, no proxy, no member credentials and no OAuth token. Five full records arrive with works, affiliations and funding attached.

**Agent skill: [SKILL.md](https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/orcid-researcher-scraper.md)**

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/orcid-researcher-scraper.md
```

### What you get

| Field | What it holds |
|---|---|
| `orcid`, `url` | The iD and the public profile link |
| `givenNames`, `familyName`, `creditName`, `otherNames` | Preferred name, published name, and every alias the researcher has registered |
| `biography` | Free-text bio when the researcher has made it public |
| `emails` | Public email addresses. Almost always empty, because ORCID defaults emails to private |
| `researcherUrls` | Named links: LinkedIn, Google Scholar, lab page, personal site |
| `keywords` | Self-declared research keywords |
| `countries` | Country codes from the researcher's registered addresses |
| `currentAffiliation` | The open-ended employment, most recent first, which is what a profile page shows at the top |
| `employments`, `educations` | Full history: organisation, department, role, start and end dates, city, region, country, ROR or Ringgold ID |
| `fundings`, `fundingCount` | Grants and awards with funder, type, dates and grant number |
| `works`, `worksCount` | Publications with title, type, publication date, DOI, journal and source. `worksCount` is the true total even when `works` is capped |
| `peerReviewCount` | Number of reviews recorded against the record |
| `createdAt`, `lastModified` | When the ORCID record was registered and last edited |
| `scrapedAt` | Run timestamp |

`RUN_SUMMARY` in the key-value store holds per-run counts, how many iDs came from search, the filters you used, and any iD that failed.

### Use cases

- **Credential verification at scale.** Check a claimed affiliation against the researcher's own registry entry.
- **Expert finding.** Search by keyword and institution, then rank by works count and recency.
- **Affiliation mapping.** Build an institution-to-researcher graph using ROR identifiers rather than fuzzy name strings.
- **RAG and knowledge bases.** Clean, permissively reusable scholarly identity metadata with DOI-level joins.
- **Bibliometrics.** Join `works[].doi` to Crossref, OpenAlex or Europe PMC for citation data ORCID does not carry.

### Quick start

One researcher, everything:

```json
{
 "orcidIds": ["0000-0002-1825-0097"],
 "contactEmail": "you@example.com"
}
```

Several iDs, identity only, small and fast:

```json
{
 "orcidIds": ["0000-0002-1825-0097", "https://orcid.org/0000-0001-5109-3700"],
 "includeWorks": false,
 "includeFunding": false,
 "contactEmail": "you@example.com"
}
```

Discover researchers by institution and topic, then fetch them in full:

```json
{
 "searchQuery": "affiliation-org-name:\"ETH Zurich\" AND keyword:genomics",
 "maxSearchResults": 100,
 "maxWorks": 50,
 "contactEmail": "you@example.com"
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `orcidIds` | array | `[]` | iDs as `0000-0000-0000-0000`, or full `https://orcid.org/.` URLs. Both are normalised. |
| `searchQuery` | string | none | Runs ORCID's public search and fetches every iD it returns. Combines with `orcidIds`, and the two sets are merged and de-duplicated. |
| `maxSearchResults` | integer | `25` | Ceiling on iDs the search may contribute. Each costs one record request. |
| `includeAffiliations` | boolean | `true` | Attach `employments` and `educations`. |
| `includeWorks` | boolean | `true` | Attach `works`. This is what makes records large. |
| `includeFunding` | boolean | `true` | Attach `fundings`. |
| `maxWorks` | integer | `200` | Cap on attached works, newest first. `worksCount` still reports the real total. |
| `contactEmail` | string | none | Goes into the User-Agent. Optional but recommended. |

Supply `orcidIds`, `searchQuery`, or both. With neither, the run fails immediately with a message saying so.

### Output example

```json
{
 "orcid": "0000-0001-5109-3700",
 "url": "https://orcid.org/0000-0001-5109-3700",
 "givenNames": "Laurel",
 "familyName": "Haak",
 "creditName": "Laurel L Haak",
 "otherNames": ["L Haak", "Laure Haak", "ローレル・ハーク"],
 "biography": "Laurel (Laure) L. Haak (she/they) is an entrepreneur, strategist, researcher, and author.",
 "emails": [],
 "researcherUrls": [
 { "name": "LinkedIn", "url": "https://www.linkedin.com/in/laurellhaak/" },
 { "name": "Google Scholar", "url": "https://scholar.google.com/citations?hl=en&user=fFjR4DoAAAAJ" }
 ],
 "keywords": ["persistent identifiers", "research policy", "multi stakeholder governance"],
 "countries": ["US"],
 "currentAffiliation": "Ronin Institute for Independent Scholarship 2.0",
 "employments": [
 {
 "type": "employment",
 "organization": "Ronin Institute for Independent Scholarship 2.0",
 "department": null,
 "roleTitle": "Research Scholar",
 "startDate": "2025-11",
 "endDate": null,
 "city": "Sacramento",
 "region": "California",
 "country": "US",
 "organizationId": "https://ror.org/000xkb537",
 "organizationIdSource": "ROR",
 "url": null
 }
 ],
 "educations": [
 {
 "type": "education",
 "organization": "Stanford University School of Medicine",
 "department": "Neurosciences",
 "roleTitle": "PhD",
 "startDate": null,
 "endDate": "1997",
 "city": "Stanford",
 "region": "California",
 "country": "US",
 "organizationId": null,
 "organizationIdSource": null,
 "url": null
 }
 ],
 "fundings": [
 {
 "title": "ADVANCE Leadership Award: Women in Science and Engineering",
 "type": "grant",
 "organization": "National Science Foundation - Directorate for Education and Human Resources",
 "country": "US",
 "startDate": "2006-04-01",
 "endDate": "2007-03-31",
 "grantNumber": "0536999",
 "url": null
 }
 ],
 "fundingCount": 4,
 "works": [
 {
 "title": "FIELD DAY - Open Research Infrastructure for Education Research with Digital Games",
 "subtitle": null,
 "type": "report",
 "publicationDate": "2025-11-14",
 "journalTitle": null,
 "doi": "10.17613/BMHWG-WXZ66",
 "url": "https://works.hcommons.org/doi/10.17613/bmhwg-wxz66",
 "putCode": 222309679,
 "source": "Laurel L Haak"
 }
 ],
 "worksCount": 227,
 "peerReviewCount": 2,
 "createdAt": "2012-09-28T17:19:08.336Z",
 "lastModified": "2026-07-30T19:09:40.855Z",
 "scrapedAt": "2026-08-06T12:00:00.000Z"
}
```

Dates from ORCID are frequently partial: a bare `1997`, or `2025-11` with no day. They are passed through as written rather than padded to a fake full date.

### Search syntax

`searchQuery` goes straight to ORCID's public search index, which speaks Solr syntax. Plain words search the whole record, and field prefixes narrow it:

| Query | Finds |
|---|---|
| `Hopper` | Anything in the record matching "Hopper" |
| `family-name:Hopper AND given-names:Grace` | A specific person by name |
| `affiliation-org-name:"ETH Zurich"` | Everyone who lists that institution |
| `keyword:bioinformatics` | Everyone with that self-declared keyword |
| `digital-object-ids:"10.1038/nature12373"` | The authors who claimed a specific DOI |
| `ringgold-org-id:5468` | Everyone at an organisation by its Ringgold ID |

Quote multi-word values. A malformed query is reported as a syntax error rather than being retried into a timeout.

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~orcid-researcher-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "orcidIds": ["0000-0002-1825-0097"],
 "maxWorks": 25,
 "contactEmail": "you@example.com"
 }'
```

### JavaScript example

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/orcid-researcher-scraper').call({
 searchQuery: 'affiliation-org-name:"ETH Zurich" AND keyword:genomics',
 maxSearchResults: 50,
 maxWorks: 20,
 contactEmail: 'you@example.com',
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const r of items) {
 console.log(`${r.givenNames} ${r.familyName} - ${r.currentAffiliation} - ${r.worksCount} works`);
}
```

### Limits and behaviour

- Every request carries `Accept: application/json`. ORCID's v3.0 API defaults to XML, and without that header you get a document that looks nothing like this.
- One bad iD won't kill the run. A deactivated record answers `409` and an unknown iD `404`. Both are recorded in `RUN_SUMMARY.failures` and the run continues, so the Actor only errors out if every iD fails.
- Works are sorted newest first before truncation. ORCID returns them in its own display order, so capping without sorting would keep an arbitrary slice.
- `worksCount` is never capped. It counts the groups ORCID returned, so it stays honest even when `maxWorks` trims the list to 20.
- Grouped works are collapsed. One publication claimed by Crossref, Scopus and the researcher is one group and one row, the preferred summary. DOIs are read from the group's shared external IDs first.
- Requests are spaced 200 ms apart, with three retries on 429 and 5xx.
- Public data only. Everything here is what the researcher chose to publish. Private and trusted-party-only fields are never returned by this API and are not visible to this Actor.

### FAQ

**Do I need an ORCID member API key?** No. You supply no credentials.

**Why is `emails` empty?** Because ORCID defaults email visibility to private, and most researchers leave it that way. When somebody has made theirs public it appears here.

**Can I search by DOI?** Yes. Put `digital-object-ids:"10.1038/nature12373"` in `searchQuery` and you get the ORCID iDs of everyone who has claimed that paper.

**How many researchers can I fetch in one run?** There is no hard cap. Records are fetched sequentially at roughly four a second, so a thousand takes about five minutes. The default timeout is 30 minutes.

**Does it include citation counts?** No. ORCID records identity and claimed works, not citations. Join on `works[].doi` against Crossref or OpenAlex for that.

**Is scraping ORCID allowed?** Yes. The public API exists for this, ORCID publishes its data under CC0, and this Actor identifies itself in the User-Agent as their documentation asks.

# Actor input Schema

## `orcidIds` (type: `array`):

ORCID iDs in 0000-0000-0000-0000 form. Full profile URLs like https://orcid.org/0000-0002-1825-0097 are accepted and trimmed to the iD automatically.

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

Find researchers instead of naming them. Plain words search the whole record; field syntax narrows it, as in family-name:Hopper, affiliation-org-name:"ETH Zurich" or keyword:bioinformatics. Every iD found is fetched in full.

## `maxSearchResults` (type: `integer`):

How many iDs the search may return. Each one costs a full record request, so this is the main driver of run time. Only used when a search query is set.

## `includeAffiliations` (type: `boolean`):

Attach employment and education history with organisation, department, role title, dates and location. Turn off for a name-and-identity-only dataset.

## `includeWorks` (type: `boolean`):

Attach the publication list with titles, types, years and DOIs. Prolific researchers have hundreds of entries, so this is the field that makes records large.

## `includeFunding` (type: `boolean`):

Attach grants and awards with funder, title, amount, dates and grant number. Most records have none, because the section is optional for researchers to fill in.

## `maxWorks` (type: `integer`):

Cap the works attached to each record, newest first. Only used when "Include works" is on. worksCount always reports the true total regardless of this cap.

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

Added to the User-Agent sent to ORCID. Not required, but scholarly APIs treat identified traffic better than anonymous traffic and it costs you nothing.

## Actor input object example

```json
{
  "orcidIds": [
    "0000-0002-1825-0097",
    "https://orcid.org/0000-0001-5109-3700"
  ],
  "searchQuery": "affiliation-org-name:\"ETH Zurich\" AND keyword:genomics",
  "maxSearchResults": 25,
  "includeAffiliations": true,
  "includeWorks": true,
  "includeFunding": true,
  "maxWorks": 200,
  "contactEmail": "you@example.com"
}
```

# Actor output Schema

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

Every record the run produced.

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

The RUN_SUMMARY record from the run's key-value store.

# 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 = {
    "orcidIds": [
        "0000-0002-1825-0097"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/orcid-researcher-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 = { "orcidIds": ["0000-0002-1825-0097"] }

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/orcid-researcher-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 '{
  "orcidIds": [
    "0000-0002-1825-0097"
  ]
}' |
apify call arman-bd/orcid-researcher-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/orcid-researcher-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/Jvs7mOL5NilgTk2Hw/builds/rHVrual9yrZFhhHw1/openapi.json
