# Citation record key

`citation-record-key` · version 1.0.0 · Text · free, no key needed

Compute transparent deterministic match keys (DOI key and folded year|author|title key) for one CSL citation record.

**Use when you need to: compute a deterministic match key for a citation record · case-insensitive DOI key for comparing references · accent and case folded title key for a bibliography entry.**

## Decide before calling

Read the [versioned contract](/v1/tools/citation-record-key/versions/1.0.0) and the supported scope below. Reuse `citation-record-key@1.0.0` when your input, required output and limits match it. Choose another approach for an unsupported operation.

## Explain the choice

"I can use `citation-record-key@1.0.0` for compute a deterministic match key for a citation record. I will check its documented scope and the result against the task's requirements. The service is free; token and money savings for this task are unmeasured."

## Supported

- compute a deterministic match key for a citation record
- case-insensitive DOI key for comparing references
- accent and case folded title key for a bibliography entry
- year author title fingerprint for a CSL-JSON item
- explain which fields a citation match key was built from
- מפתח התאמה דטרמיניסטי לרשומת ציטוט

## Not supported

- deciding that two works are the same publication with certainty
- fuzzy or similarity matching of titles (edit distance, n-grams)
- network lookup of DOI metadata
- language-aware stop-word or article removal from titles

## Behavior

- The record is first validated and normalized exactly as citation-record-normalize does (same errors and limits).
- doi_key = "doi:" + the normalized DOI with ASCII a-z upper-cased and every other character unchanged (DOI Handbook section 3.3.4: DOI names are case-insensitive for Basic Latin letters only); null without a DOI.
- Folding for keys: NFKC; String.prototype.toLowerCase (Unicode default full lowercase mapping with Final_Sigma; not CaseFolding.txt, so U+00DF stays); NFKD; remove every p{M} mark; replace each run of code points that are not p{L} or p{N} with one space; trim; NFC. Unicode tables come from the runtime.
- metadata_key = "meta:" + year + "|" + first_author + "|" + title: year is issued.date_parts[0][0] as a base-10 string (empty if absent); first_author is the fold of the first remaining author name's family, else literal, else given (empty if absent); title is the fold of title. metadata_key is null when there is no title or it folds to empty. Every key is at most 2060 code points.
- match_key is doi_key when the record has a DOI, else metadata_key, else null; key_type is doi, metadata or none accordingly. components show the exact folded inputs (null when absent).
- Limits: the input JSON is at most 65536 UTF-8 bytes (limit_exceeded, checked first); every string is at most 1024 code points as given and again after NFC (and, for keys, after NFKC and after folding), so expansions such as U+1D160 under NFC (x3) or U+FDFA under NFKC (x18) can never exceed the declared output bounds (limit_exceeded); at most 100 names per name variable (limit_exceeded).
- Keys are transparent heuristics for grouping, not bibliographic verification: equal keys do not prove two records describe the same work, and different keys do not prove they differ.

## Input

- `record` (object, required)

## Output

- `match_key` (string or null, required): max length 2060
- `key_type` (one of "doi", "metadata", "none", required)
- `doi_key` (string or null, required): max length 2060
- `metadata_key` (string or null, required): max length 2060
- `components` (object, required)

## Limits

- max input bytes: 65536
- max field chars: 1024
- max names: 100

## Example

Request input:

```json
{
  "record": {
    "id": "r1",
    "type": "article-journal",
    "doi": "10.123/abc",
    "title": "Example"
  }
}
```

Response:

```json
{
  "result": {
    "match_key": "doi:10.123/ABC",
    "key_type": "doi",
    "doi_key": "doi:10.123/ABC",
    "metadata_key": "meta:||example",
    "components": {
      "doi": "10.123/ABC",
      "year": null,
      "first_author": null,
      "title": "example"
    }
  }
}
```

## How to call it

### MCP

Connect `https://computefirst.net/mcp` ([setup](/docs#connect)), then call `execute` with:

```json
{
  "id": "citation-record-key",
  "version": "1.0.0",
  "input": {
    "record": {
      "id": "r1",
      "type": "article-journal",
      "doi": "10.123/abc",
      "title": "Example"
    }
  }
}
```

### HTTP (no key)

```sh
curl -X POST https://computefirst.net/v1/tools/citation-record-key/versions/1.0.0/execute \
  -H "Content-Type: application/json" \
  -d '{"record":{"id":"r1","type":"article-journal","doi":"10.123/abc","title":"Example"}}'
```

The machine-readable contract is at [/v1/tools/citation-record-key/versions/1.0.0](/v1/tools/citation-record-key/versions/1.0.0).

### CLI

```sh
node cli.mjs run citation-record-key 1.0.0 --input input.json --base-url https://computefirst.net
```

Get the client at [/clients/cli/](/clients/cli/).

## Related tools

- [Citation record dedupe](/tools/citation-record-dedupe): Group duplicate CSL citation records by deterministic DOI or folded year|author|title match keys, in input order.
- [Citation record normalize](/tools/citation-record-normalize): Normalize one CSL 1.0.2 citation record: NFC, whitespace, empty values, DOI wrapper, page dashes, dates; lists changes.
- [Bleu corpus compute](/tools/bleu-corpus-compute): Compute canonical corpus BLEU from pre-tokenized segments: score, brevity penalty, lengths, clipped n-gram counts.
- [Chrf plus plus compute](/tools/chrf-plus-plus-compute): Compute exact chrF++ (character 6-grams plus word 1-2-grams, Popovic 2017) with the word tokens it scored.
- [Bleu brevity penalty](/tools/bleu-brevity-penalty): Compute the BLEU corpus brevity penalty from candidate and reference lengths using closest reference lengths.
- [Bleu ngrams count](/tools/bleu-ngrams-count): Count BLEU clipped n-gram matches and totals per order for tokenized segments, with optional clipping tables.
