# Scholarly Correction Monitor (`flintglade/scholarly-correction-monitor`) Actor

Check DOI lists against public Crossref updates and Retraction Watch records, then emit one deterministic correction-status row per DOI with provenance, warnings, and stable evidence hashes. Invalid or source-failed DOI checks are uncharged.

- **URL**: https://apify.com/flintglade/scholarly-correction-monitor.md
- **Developed by:** [Flintglade](https://apify.com/flintglade) (community)
- **Categories:** News, Automation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 doi results

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Scholarly Correction Monitor

Watches a bounded DOI watchlist for Crossref-deposited corrections, retractions, expressions of
concern, withdrawals, and other updates, and reports what changed since the prior run. Built for
research teams, librarians, systematic-review maintainers, and evidence-product builders.

This is an unofficial metadata monitoring tool. It is not affiliated with or endorsed by
Crossref or Retraction Watch.

Full behavioral contract: [PRODUCT-SPEC.md](PRODUCT-SPEC.md).

### Why two Crossref requests per DOI

Crossref's own documented data model places the `update-to` field on the **notice** record
(the correction/retraction/etc.), where it points backward at the DOI it updates -- not on the
original work. A direct lookup of an original DOI therefore frequently exposes no `update-to` at
all, even when deposited updates exist:

- Direct lookup of `10.1177/1758835920922055` (`GET /v1/works/10.1177%2F1758835920922055`) returns
  no `update-to`.
- Crossref's update-discovery query,
  `GET /v1/works?filter=updates:10.1177/1758835920922055`, returns notice
  `10.1177/17588359231172420`, which carries the retraction record.

This Actor fetches **both** endpoints for every watched DOI: the direct lookup for bounded
bibliographic metadata, and the update-discovery query as the sole, authoritative source of
`currentStatus` and deposited updates. `no-deposited-update` in this Actor's output means both
sources were checked and neither exposed a recognized update -- never a direct-lookup-only guess.
Even a direct 404 still runs bounded update discovery; this prevents a missing direct record from
hiding a notice that explicitly targets that DOI.
If a watched DOI is itself an update notice, that edge case is flagged with a warning rather than
misreported as the notice's own status (see PRODUCT-SPEC.md).

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `dois` | array of strings | *(required)* | 1-500 DOIs or `doi.org` URLs. Deduplicated, first-seen order preserved. |
| `baselineMode` | `"compare"` \| `"snapshot-only"` | `"compare"` | `snapshot-only` records this run without diffing against saved history. |
| `stateNamespace` | string | `"default"` | Scopes the saved baseline; use distinct namespaces for unrelated watchlists. |
| `resetBaseline` | boolean | `false` | Ignore prior state for watched DOIs this run and replace only their successful per-DOI records. Failed, skipped, and unrelated records are preserved. |
| `maxConcurrency` | integer 1-3 | `3` | Bounded by Crossref's polite-pool limits. |
| `requestTimeoutSeconds` | integer 5-30 | `15` | Per-request timeout. |

### Output

One dataset item per watched DOI: bibliographic facts (title, publisher, type, issued date,
container title), `currentStatus`, normalized deposited updates, `changeType` versus the saved
baseline, content/status/metadata hashes, warnings, retry count, and an error code where
applicable. Full field list: [PRODUCT-SPEC.md](PRODUCT-SPEC.md#output-contract) and
[.actor/dataset_schema.json](.actor/dataset_schema.json).

A run summary and the current watchlist's baseline snapshot are stored in the run's default
key-value store. Authoritative state uses one hashed key-value record per DOI, so overlapping runs
on different DOI subsets cannot overwrite one another. No abstracts, author emails, or raw Crossref payloads are ever stored -- see
[SECURITY.md](SECURITY.md).

### Pricing

Pay per event: the synthetic `apify-actor-start` event (Apify default pricing) plus `doi-result`
at $0.003 per DOI that was successfully fetched or explicitly resolved as not-found. Source errors
after retries are emitted as free diagnostics, and DOIs skipped once the run's spending limit was
reached are never fetched or charged. Paid capacity is reserved before source work. See
[PRODUCT-SPEC.md](PRODUCT-SPEC.md#billing) and [LIMITS.md](LIMITS.md).

### Local development

Requires Node.js 22+.

````

npm install
npm run lint
npm run typecheck
npm test              # unit + integration tests (mocked HTTP, no network)
npm run fixture-run    # deterministic offline run through the real request/parsing pipeline
npm run smoke           # bounded LIVE Crossref smoke -- real network, run manually only
npm start                # runs the Actor locally (needs local Apify storage emulation)

````

`npm run smoke` is not part of `npm test` and is never run in CI; it makes a small number of real,
polite requests to the live Crossref API using the two DOIs documented in PRODUCT-SPEC.md.

### More docs

- [SECURITY.md](SECURITY.md) -- permissions, data handling, privacy.
- [LIMITS.md](LIMITS.md) -- technical and product limits, including the `no-deposited-update` limitation.
- [CHANGELOG.md](CHANGELOG.md) -- version history.

### Rollback

If a published build misbehaves, revert traffic to the last known-good build on the Apify
platform (Actor -> Builds -> select the previous tagged build -> "Publish this build" /
repoint the `latest` tag), which requires no code change. To roll back the source itself, `git
revert` the offending commit(s) on `main` and publish a new build from the reverted state; do not
force-push over published history. Because state (the saved baseline) is namespaced by
`stateNamespace` and keyed by DOI, rolling back the Actor code never corrupts previously saved
baselines -- at worst, a run under the reverted version reports `metadata-changed` or
`status-changed` once against a baseline written by a newer extraction version, which self-heals
on the next run.

### Support

Support: support@flintglade.com

# Actor input Schema

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

1-500 DOI strings or doi.org URLs to watch. Duplicates are removed and first-seen order is preserved.
## `baselineMode` (type: `string`):

"compare" diffs this run's results against the previously saved baseline for the state namespace. "snapshot-only" records the current observation without comparing against history.
## `stateNamespace` (type: `string`):

Stable identifier (1-64 characters: letters, digits, underscore, hyphen) that scopes the saved baseline. Use different namespaces to track unrelated watchlists independently.
## `resetBaseline` (type: `boolean`):

Ignore prior state for the watched DOIs during this run, report successful observations as first observations, and replace only those per-DOI records. Failed, skipped, and unrelated DOI records are preserved.
## `maxConcurrency` (type: `integer`):

Maximum number of simultaneous Crossref requests, honoring Crossref's polite-pool limits.
## `requestTimeoutSeconds` (type: `integer`):

Per-request Crossref timeout in seconds.

## Actor input object example

```json
{
  "dois": [
    "10.1177/1758835920922055"
  ],
  "baselineMode": "compare",
  "stateNamespace": "default",
  "resetBaseline": false,
  "maxConcurrency": 3,
  "requestTimeoutSeconds": 15
}
````

# Actor output Schema

## `results` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# 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.1177/1758835920922055"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("flintglade/scholarly-correction-monitor").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.1177/1758835920922055"] }

# Run the Actor and wait for it to finish
run = client.actor("flintglade/scholarly-correction-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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.1177/1758835920922055"
  ]
}' |
apify call flintglade/scholarly-correction-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=flintglade/scholarly-correction-monitor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Scholarly Correction Monitor",
        "description": "Check DOI lists against public Crossref updates and Retraction Watch records, then emit one deterministic correction-status row per DOI with provenance, warnings, and stable evidence hashes. Invalid or source-failed DOI checks are uncharged.",
        "version": "0.1",
        "x-build-id": "bH7p8LUskKaXTlXE7"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/flintglade~scholarly-correction-monitor/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-flintglade-scholarly-correction-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/flintglade~scholarly-correction-monitor/runs": {
            "post": {
                "operationId": "runs-sync-flintglade-scholarly-correction-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/flintglade~scholarly-correction-monitor/run-sync": {
            "post": {
                "operationId": "run-sync-flintglade-scholarly-correction-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "dois"
                ],
                "properties": {
                    "dois": {
                        "title": "DOIs to watch",
                        "minItems": 1,
                        "maxItems": 500,
                        "type": "array",
                        "description": "1-500 DOI strings or doi.org URLs to watch. Duplicates are removed and first-seen order is preserved.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "baselineMode": {
                        "title": "Baseline mode",
                        "enum": [
                            "compare",
                            "snapshot-only"
                        ],
                        "type": "string",
                        "description": "\"compare\" diffs this run's results against the previously saved baseline for the state namespace. \"snapshot-only\" records the current observation without comparing against history.",
                        "default": "compare"
                    },
                    "stateNamespace": {
                        "title": "State namespace",
                        "pattern": "^[A-Za-z0-9_-]{1,64}$",
                        "minLength": 1,
                        "maxLength": 64,
                        "type": "string",
                        "description": "Stable identifier (1-64 characters: letters, digits, underscore, hyphen) that scopes the saved baseline. Use different namespaces to track unrelated watchlists independently.",
                        "default": "default"
                    },
                    "resetBaseline": {
                        "title": "Reset baseline",
                        "type": "boolean",
                        "description": "Ignore prior state for the watched DOIs during this run, report successful observations as first observations, and replace only those per-DOI records. Failed, skipped, and unrelated DOI records are preserved.",
                        "default": false
                    },
                    "maxConcurrency": {
                        "title": "Max concurrency",
                        "minimum": 1,
                        "maximum": 3,
                        "type": "integer",
                        "description": "Maximum number of simultaneous Crossref requests, honoring Crossref's polite-pool limits.",
                        "default": 3
                    },
                    "requestTimeoutSeconds": {
                        "title": "Request timeout (seconds)",
                        "minimum": 5,
                        "maximum": 30,
                        "type": "integer",
                        "description": "Per-request Crossref timeout in seconds.",
                        "default": 15
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
