# eCFR Historical Citation Review (`remarkable_whipscorpion/ecfr-historical-citation-review`) Actor

Historical source-change packets for existing document-to-CFR citation tables, with exact text hunks and source coverage for human review.

- **URL**: https://apify.com/remarkable_whipscorpion/ecfr-historical-citation-review.md
- **Developed by:** [Kagerou AP](https://apify.com/remarkable_whipscorpion) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 90.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $250.00 / 1,000 completed changed citation packets

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

## eCFR Historical Citation Review

Prepare a historical source-change packet for your existing document-to-CFR reference table. Supply document IDs, exact CFR title/section citations, and two review dates. The Actor joins the official eCFR text comparison back to each document ID, with headings, text hunks, source URLs, editorial record dates, title currency and unresolved references.

For device QA consultants reviewing controlled-document references. The Actor reports source facts for human review; it does not decide whether an SOP must change or whether a device, document or organization complies with law.

### Try the historical example

```json
{
  "references": [{ "documentId": "SOP-DEMO-001", "cfrTitle": 21, "section": "820.1" }],
  "beforeDate": "2025-01-01",
  "afterDate": "2026-03-01",
  "maxReferences": 20
}
```

This original demonstration document ID maps to real official government source text. It deliberately uses historical dates. The default, prefill, API example and sample input are identical. The real sample output reports a change to 21 CFR 820.1 and contains the returned text rather than fabricated regulatory text. It is a demonstration of source comparison, not an actual customer's SOP or measured time saving.

### Output

The default Dataset contains one `citation_review_packet`. Key-value store record `OUTPUT.packet` contains the same useful facts; the `OUTPUT` envelope also contains publication and custom-event billing status.

Each reference retains the supplied `documentId`, `cfrTitle` and `section`. Status is `changed`, `unchanged`, `source-record-removal` or `unavailable`. `before` and `after` retain requested dates, normalized heading/text, exact source URLs, source hashes and matching editorial source records when available. `textHunks` contains added, removed and context text. `sourceCurrency.up_to_date_as_of` is the official title's current coverage date. The source log date 2026-02-04 in the example is an editorial source-record date; it must not be treated as a legal effective date.

`source-record-removal` means the exact record is missing and the matching official version record says `removed: true`. It does not mean a legal obligation ended. An unexplained 404, invalid XML, wrong citation, stale currency, incomplete version log, network failure or request cap becomes `unavailable`. An unresolved reference makes the packet `partial_unresolved`; source changes already retrieved remain visible with no custom-event fee. Unchanged is reported only when complete comparable source facts establish it, including identical officially removed-record states.

### Price and budget

Proposed Pay-per-event price: **$0.25 per completed changed citation packet**, plus **$0.00005 automatic Actor start**. There is no Dataset item fee. The custom event is exactly `completed_changed_citation_packet`, one per full packet, regardless of the number of changed document references.

All requested valid references must be resolved and at least one source change must be present. Unchanged, unavailable, partial unresolved and error results have zero custom-event fee. An invalid or missing input is rejected before source retrieval or custom charging. Platform startup and infrastructure charges are separate.

Funded whole-event capacity is determined before saving useful results. A budget below $0.25005 (when one startup event is due) cannot fund a changed packet; it produces only a budget diagnostic with no source text. Useful output is persisted before the custom event. `OUTPUT` tracks settled charges. If a publication or charge response is uncertain, resurrection does not replay it. The checkpoint is left for inspection; do not delete it and retry an uncertain charge in the same run. Local SDK execution is a free local test and does not demonstrate native customer billing or owner-waiver behavior.

Resuming a completed packet checks that the default Dataset still contains the same packet. Missing, unreadable or different Dataset content rejects the resume without another source request, packet write or custom event. If `OUTPUT` is missing while Dataset content or a prior custom-event count survives, the run stops without reconstructing the report or repeating the work. Unsupported root input fields are rejected before source retrieval.

### Limits and sources

Up to 20 input references, 45 serial official API requests, 400,000 response bytes each, 8 MB total and a 180-second source-fetch window; per-request timeout is 12 seconds. Text comparisons have a 1-second/1,000-edit bound; a comparison exceeding that bound is unresolved. One large version log or many distinct parts may produce an unresolved packet within these bounds. Duplicate citations with different document IDs reuse source fetches while keeping each document ID. Identical document/citation rows are rejected.

Dates must be real `YYYY-MM-DD` calendar dates from 2017-01-01, in chronological order, no later than today and no later than each official title's currency. Dates are never silently clamped. Accepted citations are exact sections such as `820.1` or `11.1a`, not whole parts, ranges, appendices or inferred crosswalks. Section prefixes identify the part for official version lookup; an unavailable or inconsistent part log leaves the reference unknown. No title-wide or legal-completeness claim is made.

Only these official endpoints are requested:

- [Title currency](https://www.ecfr.gov/api/versioner/v1/titles.json)
- [Exact earlier section XML](https://www.ecfr.gov/api/versioner/v1/full/2025-01-01/title-21.xml?section=820.1)
- [Exact later section XML](https://www.ecfr.gov/api/versioner/v1/full/2026-03-01/title-21.xml?section=820.1)
- [Official part version log](https://www.ecfr.gov/api/versioner/v1/versions/title-21.json?part=820)

The eCFR is an informational compilation, not an official legal edition; see [What is the eCFR?](https://www.ecfr.gov/reader-aids/understanding-the-ecfr/what-is-the-ecfr) and [point-in-time guidance](https://www.ecfr.gov/reader-aids/using-ecfr). Government regulatory text is the public-domain source baseline. Private incorporated standards such as ISO 13485 and ISO 9000 are not fetched, followed, reconstructed or licensed by this Actor. Their identifiers may occur in official section text. No browser, login, proxy, notification, external paid API, retained monitoring baseline or private document upload is required. HTTP 403/429 stops source requests without bypass or retry.

### Local development and licensing

Node 24.18.0; `npm ci --ignore-scripts`, `npm test`, `npm run validate`, `npm run live`. Tests launch a child process with `CRAWLEE_STORAGE_DIR` already set to a new SKU-only directory, assert the actual SDK storage base and never purge/drop shared storage. Live testing reads the four official sample endpoints and writes evidence locally. Native Actor creation/build/run/publishing requires a separate release authorization and has not been performed for this local implementation.

Pinned libraries: Apify SDK 3.7.2 (Apache-2.0), fast-xml-parser 5.11.2 (MIT), jsdiff 9.0.0 (BSD-3-Clause). Preserve the notices in `THIRD_PARTY_NOTICES.txt` and dependency licenses. XML declarations defining entities/DOCTYPE are rejected. Shipping source and the dependency lock are frozen by the implementation manifest.

# Actor input Schema

## `references` (type: `array`):

Your existing document IDs and exact CFR title/section pairs. Up to 20; distinct document IDs may share a citation. No private document text is needed.

## `beforeDate` (type: `string`):

YYYY-MM-DD from 2017-01-01. Historical example: 2025-01-01. Must be no later than the later date.

## `afterDate` (type: `string`):

YYYY-MM-DD within current official title currency. Historical example: 2026-03-01. No future dates or silent date adjustment.

## `maxReferences` (type: `integer`):

1–20. Input exceeding this limit is rejected rather than silently truncated.

## Actor input object example

```json
{
  "references": [
    {
      "documentId": "SOP-DEMO-001",
      "cfrTitle": 21,
      "section": "820.1"
    }
  ],
  "beforeDate": "2025-01-01",
  "afterDate": "2026-03-01",
  "maxReferences": 20
}
```

# Actor output Schema

## `packet` (type: `string`):

No description

## `output` (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 = {
    "references": [
        {
            "documentId": "SOP-DEMO-001",
            "cfrTitle": 21,
            "section": "820.1"
        }
    ],
    "beforeDate": "2025-01-01",
    "afterDate": "2026-03-01",
    "maxReferences": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("remarkable_whipscorpion/ecfr-historical-citation-review").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 = {
    "references": [{
            "documentId": "SOP-DEMO-001",
            "cfrTitle": 21,
            "section": "820.1",
        }],
    "beforeDate": "2025-01-01",
    "afterDate": "2026-03-01",
    "maxReferences": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("remarkable_whipscorpion/ecfr-historical-citation-review").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 '{
  "references": [
    {
      "documentId": "SOP-DEMO-001",
      "cfrTitle": 21,
      "section": "820.1"
    }
  ],
  "beforeDate": "2025-01-01",
  "afterDate": "2026-03-01",
  "maxReferences": 20
}' |
apify call remarkable_whipscorpion/ecfr-historical-citation-review --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,remarkable_whipscorpion/ecfr-historical-citation-review"
        }
    }
}
```

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/uOMYdbCVFNURE7e6T/builds/cq19JgdsSQGvyCtWB/openapi.json
