# MARC Batch Triage (`l3digital/marc-batch-triage`) Actor

Experimental bounded deterministic triage of authorized inline MARC21 UTF-8 or MARCXML bibliographic batches. Finite leader and 245 checks; no repair or recipient import certification.

- **URL**: https://apify.com/l3digital/marc-batch-triage.md
- **Developed by:** [L3Digital](https://apify.com/l3digital) (community)
- **Categories:** Developer tools, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.05 / useful marc triage receipt

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

### What does MARC Batch Triage do?

Locate selected structure, leader and title-field (`245`) issues before routing
a small bibliographic batch to cataloging review. Supply authorized inline
MARC21 UTF-8 or MARCXML; one deterministic report identifies affected records,
fields and indicators, records completed and skipped checks, and gives concrete
review actions. Its finite
catalog follows the [Library of Congress MARC21 bibliographic format](https://www.loc.gov/marc/bibliographic/).

This experimental Actor does not repair records, validate all MARC semantics or
certify that a recipient catalog will import a batch. Its resource limits are
fixed; hosted performance, customer savings and commercial advantage are not
guaranteed.

### Why use MARC Batch Triage?

The intended caller is a library or vendor integration agent restricted to
HTTP/API or MCP tools. A bounded hosted call could supply a localized review
receipt without installing a MARC parser in that caller's environment. That access
convenience remains a hypothesis: measured time savings, customer demand and a
commercial advantage have not been established.

MarcEdit, PyMARC, Koha/FOLIO staging and MARC AI remain strong alternatives. Prefer
an existing local or catalog workflow when it supplies the checks, contextual
matching or repair you need. This Actor checks only the scope below and never
fetches records, accepts catalog credentials or writes to a recipient catalog.

### How to triage an authorized MARC batch

1. Confirm that you may transmit and process the records on Apify, including any
   private local fields. Review the retention section before uploading.
2. Try the synthetic demo, then choose one inline representation for your real
   batch. The input examples below show the exact key relationships.
3. When running this Actor, submit the selected input through your authorized
   Apify caller. See the Input tab for the form fields and the API tab for
   programmatic invocation details.
4. Retrieve the existing run's `REPORT` from its default key-value store or its
   one default dataset item. Inspect the report status and coverage before acting.
5. Route each finding's `actionCode` and location to cataloging review. Preserve
   the original batch and resolve uninspected portions with an appropriate tool.

#### Try the demo

```json
{"demo": true}
```

The demo runs a bundled synthetic record through the same core. Omit `format`,
`marcBase64` and `marcXml` entirely; leave `processingPermission` false or omit it.
It returns `example: true`, `useful: false` and never charges a report event.

#### Supply MARCXML

This complete illustrative input contains a synthetic bibliographic record. It
is an input example, not captured hosted execution. For your own records, set
`processingPermission` to true only after confirming upload and processing
permission.

```json
{
  "format": "marcxml",
  "marcXml": "<collection xmlns=\"http://www.loc.gov/MARC21/slim\"><record><leader>00000nam a2200000   4500</leader><controlfield tag=\"001\">synthetic-example-1</controlfield><datafield tag=\"245\" ind1=\"1\" ind2=\"0\"><subfield code=\"a\">Synthetic example title</subfield></datafield></record></collection>",
  "processingPermission": true
}
```

#### Supply binary MARC21

Use `format: "marc21"`, `marcBase64` containing your complete authorized binary
batch encoded as canonical padded standard Base64, and
`processingPermission: true`. Omit `marcXml` entirely. Base64 must contain no
whitespace, URL-safe substitutions or data-URI prefix. Binary semantic decoding
requires strict UTF-8 and Leader/09 `a`; MARC-8 and other encoding labels are
reported as unsupported coverage rather than guessed or converted.

### Input and supported checks

The Input tab lists the form fields. Runtime validation enforces their
relationships: productive input needs exactly one
matching format/payload route and explicit permission. Unknown keys, wrong JSON
types, mixed routes and explicit empty/null alternate payloads are refused.
There are no URLs, filenames, credentials, profiles or configurable rules.

MARCXML requires the exact namespace `http://www.loc.gov/MARC21/slim`, a `record`
or `collection` root, and the supported narrow element/attribute shape. DTDs,
entities and external references are refused. Schema-location declarations are
never fetched. Only absent or UTF-8 XML encoding declarations are supported.

| Check | Scope |
| --- | --- |
| Binary structure | Declared record framing, selected leader constants, directory ranges, delimiters and strict UTF-8. No silent resynchronization after a broken frame. |
| Bibliographic family | Finite Leader/06 and Leader/07 sets; unsupported record families remain explicit coverage gaps. |
| `245` cardinality | Repeated `245` fields are localized errors. An absent `245` is not a universal required-title error. |
| `245` indicators | First indicator `0` or `1`; second indicator `0` through `9`. The correctness of a nonfiling count is outside scope. |
| `245` codes and repeatability | Current selected codes, obsolete/undefined codes and repeated nonrepeatable subfields. No automatic mapping or deletion. |
| `245` context | Missing `$a` without `$k` gives informational `profile_unknown`; a `$k`-only title is permitted. |

MARCXML lacks binary directory/offset evidence, so those checks are skipped as
`representation_absent`. Unselected fields, title content/order, 006/007/008
coded semantics and other leader positions are not validated. The complete
catalog is `marc-triage-1`. Supported bibliographic Leader/06 types are
`a,c,d,e,f,g,i,j,k,m,o,p,r,t`; Leader/07 levels are `a,b,c,d,i,m,s`. Other families
remain unsupported coverage. Allowed `245` codes are `a,b,c,f,g,h,k,n,p,s,z,6,7,8`;
`k,n,p,7,8` are repeatable, and the other selected codes are nonrepeatable.

### What does the report contain?

One JSON object is stored as `REPORT`, then as one equal dataset item. Use JSON
for the nested findings and coverage; the dataset view summarizes the receipt.
The Output tab links to the complete REPORT JSON and dataset receipt.

| Field | Meaning |
| --- | --- |
| `status`, `reasonCode` | `completed`, `partial`, `refused` or `failed`, with a fixed reason where applicable. |
| `scopeComplete` | All applicable selected checks finished; this does not mean full MARC validity. |
| `example`, `useful` | Synthetic-demo flag and exact productive-report eligibility. |
| `records` | Record locations, findings, completed/skipped checks and compact coverage. |
| `summary` | Inspected/enumerated counts and reported errors/context unknowns. Unknown totals remain null. |
| `uninspectedRemainder` | Explicit remaining coverage, including a known binary byte range and unknown record count when framing cannot continue. |
| `inputBytes`, `inputSha256` | Input provenance where available; a hash is not anonymization. |
| `limitations`, `retention` | Fixed scope and storage disclosures. |

This finding is a projected excerpt from the observed **local synthetic** envelope
fixture. It is neither hosted output nor a complete report:

```json
{
  "checkId": "T01-245-indicators",
  "severity": "error",
  "messageCode": "invalid_indicator",
  "actionCode": "review_title_indicators",
  "location": {
    "recordOrdinal": 1,
    "fieldOrdinal": 1,
    "fieldOccurrence": 1,
    "tag": "245",
    "subfieldOrdinal": null,
    "indicatorPosition": 1,
    "byteOffset": null,
    "xmlPointer": "/collection/record[1]/datafield[1]"
  },
  "badCode": "9",
  "declaredFieldOffset": null
}
```

Ordinals and occurrences start at one; binary byte offsets start at zero. An
`error` identifies a selected rule fault. `profile_unknown` requests contextual
review and is not an error. Findings avoid copying title or other field values.

| Status | Caller action |
| --- | --- |
| `completed` | Review findings. Zero findings means only `no_findings_in_completed_checks`. |
| `partial` | Use retained findings and review all skipped checks, stopped locations and remaining input separately. |
| `refused` | Read the reason and correct permission, representation or resource issues before making another request. |
| `failed` | Inspect the run and existing storage; a setup/computation/output failure did not supply a useful diagnostic report. |

A stored `completed` or `partial` report can coexist with a failed platform run if
storage acknowledgement or charging is ambiguous. Retrieve existing output and
inspect the existing run before deciding on a rerun. A new invocation is a new
request and may charge again; the Actor does not automatically resume a partial
report or replay ambiguous writes/charges.

### Recover and retain this run's evidence

1. Use your authorized Apify caller to [start asynchronously](https://docs.apify.com/api/v2/actors-runs-post). Save the returned run `id` and a caller-generated request key in your own records before proceeding; retain `defaultKeyValueStoreId` and `defaultDatasetId` from the run response.
2. Check `GET /v2/actor-runs/RUN_ID` with that saved ID ([run API](https://docs.apify.com/api/v2/actor-run-get)). If a wait or retrieval times out, check the same run again. A failed or timed-out run can still have stored output; inspect it before deciding what to do.
3. Retrieve the authoritative full receipt with `GET /v2/key-value-stores/STORE_ID/records/REPORT`, using that run's `defaultKeyValueStoreId` as `STORE_ID` ([record API](https://docs.apify.com/api/v2/key-value-store-record-get)). Its equal dataset item is available at `GET /v2/datasets/DATASET_ID/items?format=json`, using `defaultDatasetId` ([dataset items API](https://docs.apify.com/api/v2/dataset-items-get)). Check `REPORT` during failure recovery even if dataset delivery failed.
4. Export the full typed JSON, parse it and verify the input binding and decision-required evidence before acting or cleaning up. Keep `reportVersion`, `catalogVersion` and `inputSha256` with the report. A `completed` report can contain findings; for `partial`, retain stopped locations, skipped checks and `uninspectedRemainder` alongside completed checks before routing cataloging review.

Do not silently POST again after a failed retrieval. If the initial start response was lost and no run ID is known, reconcile your authorized run history against your recorded request and input before considering a paid restart. A missing response does not prove no run started; this workflow promises no universal start idempotency.

A table or optional dataset field projection can support a limited notification, but it is not the full report. Retain the run/storage IDs, the identity and version/profile fields above, and relevant coverage; retrieve omitted detail from this same run before making a detailed decision.

#### Selective cleanup

After the run is terminal, no restart is planned and your exported evidence has been parsed and verified, you may remove authorized copies you no longer need. Use your saved exact storage IDs, never a latest-run shortcut or an assumed shared store. See Apify's [record deletion](https://docs.apify.com/api/v2/key-value-store-record-delete) and [dataset deletion](https://docs.apify.com/api/v2/dataset-delete) references for platform operations, which were not exercised in this Actor's QA: deleting `INPUT` leaves `REPORT`; deleting `REPORT` does not delete the dataset. Input records, REPORT records, dataset items and external copies are separate. Removing one does not erase logs, exports, saved task input, source files or customer copies, or guarantee provider-wide erasure.

### How much will MARC Batch Triage cost?

The current price is **$0.05 per useful productive report**, using
one `report-produced` event. A completed or partial report qualifies only when
at least one present
`245` on a supported, structurally readable record finishes T01–T03. Diagnosed
errors can make a report useful. Mere framing, an absent `245`, skipped checks
or context unknowns alone do not qualify.

Demo, refused and uninspected/unuseful reports do not charge a report event.
Productive paid configuration and event capacity are checked before computation.
`REPORT` and one equal dataset object must be acknowledged before charging. An
ambiguous storage or charge response fails the run without application replay;
already stored output or an accepted charge may remain.

### Fixed resource limits

| Quantity | Inclusive limit |
| --- | --- |
| Decoded binary / UTF-8 XML | 1 MiB (1,048,576 bytes). |
| Encoded binary Base64 | 1,398,104 characters. |
| Inspected records | 500; later records remain explicit uninspected coverage. |
| Fields / subfields per record | 256 fields / 1,024 subfields. |
| Field or subfield value | 16 KiB (16,384 UTF-8 bytes). |
| XML elements / depth | 50,000 / 4, including a collection root. |
| Findings | 32 per record / 4,000 per batch; unfinished checks remain visible. |
| Serialized report | 4 MiB (4,194,304 bytes); overflow fails without charge. |

These are fixed design limits, not hosted latency or memory guarantees. Raw/XML
global gates refuse an oversized input. Per-record structural limits skip the
affected record; record/finding caps preserve inspected work as partial coverage.
A binary tail with unknown framing never receives a guessed record count.

### Privacy, troubleshooting and support

Upload only records you are authorized to process. Bibliographic exports can
contain private local notes, holdings or other sensitive fields. Permission is
an attestation, not proof of rights or consent. **Normal Apify input/output
retention applies.** No automatic deletion, ephemeral upload or redaction of the
original input is promised. Input hashes do not anonymize records.

For unsupported encodings, re-export through an authorized workflow rather than
changing an encoding label. For malformed binary structure, preserve the original
and inspect the localized export bytes. For title findings, use approved cataloging
rules and source context; the Actor never selects a repair. Report unexpected
results through the Actor's Issues tab with the report's fixed reason/check codes;
avoid including private record values.

The output contract expects the run's default cloud dataset. Retrieve existing
REPORT and dataset output before deciding whether another invocation is needed.

# Actor input Schema

## `demo` (type: `boolean`):

Uses a fixed synthetic example and never charges. Omit format, marcBase64 and marcXml entirely, including empty or null values, and leave processingPermission false.

## `format` (type: `string`):

Required for a real batch. Select exactly the supplied representation; omit this key for demo.

## `marcBase64` (type: `string`):

Required only for marc21. Canonical strict base64 without whitespace, URL-safe substitutions or a data-URL prefix. At most 1,398,104 encoded characters and 1 MiB decoded bytes. Omit marcXml entirely.

## `marcXml` (type: `string`):

Required only for marcxml. Inline MARC21/slim XML, at most 1 MiB UTF-8 bytes. No external-resource access; prohibited XML constructs are refused. Omit marcBase64 entirely.

## `processingPermission` (type: `boolean`):

Must be true for a real batch. Confirm you have permission to process this inline input and accept normal Apify input/output retention. Leave false for demo.

## Actor input object example

```json
{
  "demo": true,
  "processingPermission": false
}
```

# Actor output Schema

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

One receipt object per run, including explicit coverage gaps.

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

The same object as the dataset receipt.

# 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 = {
    "demo": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("l3digital/marc-batch-triage").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 = { "demo": True }

# Run the Actor and wait for it to finish
run = client.actor("l3digital/marc-batch-triage").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 '{
  "demo": true
}' |
apify call l3digital/marc-batch-triage --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,l3digital/marc-batch-triage"
        }
    }
}
```

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/YdaSzdVSKQM4wBnfS/builds/E4vMnYsTAxutqSJkK/openapi.json
