# Crossref DOI Metadata – Clean JSON and CSV (`wavy_toucanet/crossref-metadata-pilot`) Actor

Normalize DOI lists with sourced Crossref metadata, duplicate reports, precise dates and spreadsheet-safe CSV. No LLM calls.

- **URL**: https://apify.com/wavy_toucanet/crossref-metadata-pilot.md
- **Developed by:** [kalle walle](https://apify.com/wavy_toucanet) (community)
- **Categories:** Developer tools, Education
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 normalized doi metadata

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

### Crossref DOI Metadata Pilot

Turn a bounded list of DOIs into consistent bibliographic records, with provenance, duplicate counts, and explicit error reports. Use this Actor for bibliography cleanup, spreadsheet imports, and research workflows that already have identifiers and need predictable JSON or CSV output.

All metadata comes from the fixed official endpoint `https://api.crossref.org/works/{doi}`. The Actor does not visit publishers, download papers, crawl arbitrary websites, or use an LLM to complete missing fields. Missing deposited values remain `null` and appear in `missingFields`. A DOI metadata match does not establish a paper's quality or authenticity.

### Input and first run

Supply **1–500 DOI strings**, each at most 2,048 characters. Equivalent normalized identifiers are fetched once within the batch.

```json
{
  "dois": ["10.1038/nphys1170", "https://doi.org/10.1038/NPHYS1170"],
  "maxItems": 100,
  "includeAuthors": false,
  "requestIntervalMillis": 1100,
  "requestTimeoutSeconds": 15,
  "maxRetries": 2,
  "maxRunSeconds": 600
}
```

These two inputs identify the same normalized DOI and produce at most one successful row. Bare identifiers, a leading `doi:` prefix, and resolver URLs on `doi.org`, `dx.doi.org`, or `www.doi.org` are accepted. Resolver URLs containing credentials, ports, query strings, or fragments are rejected. Free-text citations and other website URLs are unsupported.

| Field | Default | Behavior |
|---|---|---|
| `dois` | Required | Between 1 and 500 strings |
| `maxItems` | `100` | 1–500 successful unique output records; missing/invalid DOIs do not count |
| `includeAuthors` | `false` | Explicit opt-in for up to 100 deposited public author names per record |
| `requestIntervalMillis` | `1100` | 1,000–60,000 ms between sequential request starts; upstream headers can increase the wait |
| `requestTimeoutSeconds` | `15` | 1–30 seconds per HTTP request |
| `maxRetries` | `2` | 0–2 retries for network failures, HTTP 429, and HTTP 5xx |
| `maxRunSeconds` | `600` | 30–1,800 seconds before starting more lookups; the current lookup and bounded retries may finish later |

No Crossref key or contact email is required. Unknown input fields are rejected.

### Output and exports

The default Apify dataset holds successful unique records with `recordId`, `doi`, `title`, `publicationType`, `publisher`, `containerTitle`, `publicationDate`, `publicationDatePrecision`, `publicationDateParts`, `authorCount`, `authors`, `referenceCount`, `licenseUrls`, `sourceUrl`, `retrievedAt`, `missingFields`, and `warnings`.

Publication dates preserve deposited year, month, or day precision. A year-only date stays a year; no January 1 is invented. Known presentation tags are removed from selected text fields; mathematical comparisons and unknown markup remain. Length limits, markup removal and applicable text/author truncation notices appear in `warnings`.

Author names are omitted unless `includeAuthors: true`; `authors` otherwise remains an empty array. `authorCount` still counts deposited author entries. Abstracts, full texts, author profiles, affiliations, and email addresses are not output.

The default key-value store contains:

- `REPORT`: input/duplicate/emitted counts, error codes with zero-based input indexes, deferred DOIs, and stop reason.
- `STATE`: checkpoint and normalized input fingerprint for recovery in the same run/storage.
- `OUTPUT.csv`: selected-column CSV containing collected successful rows after normal processing finishes.

The dataset JSON includes all output fields. The dedicated CSV quotes cells, escapes double quotes, and prefixes potential spreadsheet formulas with an apostrophe. Array values are JSON text. This protection applies to `OUTPUT.csv`; external exporters may behave differently.

### Errors, limits, and recovery

Invalid identifiers and Crossref 404s go into `REPORT`, without becoming successful dataset items. Persistent upstream errors, rate limits, or invalid responses pause the remaining list. Inspect `deferredDois` and `stopReason` for partial output. Other stop reasons are `max_items`, `time_limit`, and `customer_budget`. Crossref response bodies larger than 2 MiB are rejected.

Recovery requires the same parsed input configuration. Existing dataset rows without a matching checkpoint stop the run. Pending dataset delivery is reconciled against actual dataset rows. An uncertain missing write stops for review instead of blindly appending again. Upstream retry deadlines are saved as `resumeAfter` and survive a restart. This reduces duplicate-output/charging risk; it is not an exactly-once billing guarantee under all platform failures. Use fresh run storage for an independent batch.

### Pricing and current pilot status

The configured pay-per-event price is **$0.001 per successful unique result ($1 per 1,000 results)**, with no Actor-start event. Check the current Pricing tab before a run and set your maximum charge in Apify. In pay-per-event mode, this implementation accepts only the automatic dataset-item event `apify-default-dataset-item`, priced at no more than $0.001 per successful result. Unexpected monetization configuration stops processing.

Errors, duplicates, invalid inputs, and skipped records are not pushed as successful result events. Separate exports or services you use in your Apify account can have their own fees. Functional tests on Apify confirmed one result event for one unique record and no extra result after resuming the same test run. These owner tests establish processing behavior; they do not establish customer payments, profit, or payouts.

### Source, use, and support

Metadata comes from [Crossref's public REST API](https://www.crossref.org/documentation/retrieve-metadata/rest-api/). Deposited records may be incomplete or outdated. A license URL in a record does not itself establish permission to reuse the paper's full text. Only process data you are entitled to use, especially when opting into author names.

Once published, report unexpected behavior through the Actor's Issues tab with a run ID, affected DOI, and `REPORT` error code. Never include credentials. This independent pilot is not affiliated with or endorsed by Crossref.

# Actor input Schema

## `dois` (type: `array`):

One to 500 DOI strings or doi.org resolver URLs. Duplicate DOIs are fetched once.

## `maxItems` (type: `integer`):

Maximum successful unique records to return. Missing and invalid DOIs are not dataset items.

## `includeAuthors` (type: `boolean`):

Explicitly request public author names from Crossref (up to 100 per record). Default output omits personal names.

## `requestIntervalMillis` (type: `integer`):

Sequential requests, at least one second apart. Upstream Retry-After pauses take precedence.

## `requestTimeoutSeconds` (type: `integer`):

Timeout for one Crossref HTTP request.

## `maxRetries` (type: `integer`):

At most two retries for network failures, HTTP 429 and HTTP 5xx. Result writes are never blindly retried.

## `maxRunSeconds` (type: `integer`):

Stop starting new DOI lookups after this time; a current request may finish first.

## Actor input object example

```json
{
  "dois": [
    "10.1038/nphys1170"
  ],
  "maxItems": 100,
  "includeAuthors": false,
  "requestIntervalMillis": 1100,
  "requestTimeoutSeconds": 15,
  "maxRetries": 2,
  "maxRunSeconds": 600
}
```

# Actor output Schema

## `dataset` (type: `string`):

Successful unique DOI metadata records.

## `report` (type: `string`):

Invalid inputs, upstream failures and deferred items.

## `csv` (type: `string`):

Spreadsheet-safe CSV; the raw platform CSV export is separate.

# 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 = {
    "dois": [
        "10.1038/nphys1170"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("wavy_toucanet/crossref-metadata-pilot").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 = { "dois": ["10.1038/nphys1170"] }

# Run the Actor and wait for it to finish
run = client.actor("wavy_toucanet/crossref-metadata-pilot").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 '{
  "dois": [
    "10.1038/nphys1170"
  ]
}' |
apify call wavy_toucanet/crossref-metadata-pilot --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,wavy_toucanet/crossref-metadata-pilot"
        }
    }
}
```

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/9WxcHazkxQCI0gtJV/builds/7i9khadDWtBsu1dgg/openapi.json
