Twenty links, ten citations: what a machine finishes and what it hands back

A reading list of twenty sources through a citation endpoint. Ten came back as complete records with RIS and BibTeX, in eight seconds. The interesting half is the other ten — because only one of them was stopped by a bot defence. Five answered every request in full and simply had no citation data to declare.

3 August 2026 · 20 sources, one pass · raw data

The question

A bibliography is the part of a piece of work where a machine looks most useful and is hardest to check. Hand an assistant twenty addresses, ask for a reference list, and something plausible comes back for all twenty. The question worth measuring is not how many entries appear. It is how many are records — read from what the page declares about itself — and whether the rest are named as gaps or quietly filled in.

Each source was sent once to extract_citation on this site's MCP endpoint, which reads the citation metadata a page publishes about itself and returns a structured record. Nothing was retried, nothing was chosen for whether it works. Every address was checked in a browser before the run, so that a typo of ours could not be counted as a failure of theirs.

10/20
complete records
1
stopped by a bot defence
5
no citation data on the page
0.4 s
per source

Where the split runs

Not between disciplines, and not between paid and free. It runs between pages built to be cited and pages built to be read.

Kind of sourceComplete records
reference2 of 2
bare doi1 of 1
publisher4 of 5
open access2 of 3
repository1 of 3
preprint0 of 1
official0 of 3
grey lit0 of 1
news0 of 1

Journal publishers are the easiest case, whether the article is paywalled or open: 4/5 and 2/3. A journal page carries citation_author, citation_title and a DOI in its head, because it wants to be indexed. An encyclopaedia entry and a bare DOI resolve just as cleanly.

Official statistics, chambers of commerce and newspapers are the hard case: none of the four produced a record. Not because they defend themselves — they answered every request in full — but because a statistics portal page is a topic overview, not a work, and declares no author, no date and no title of the kind a reference list needs.

The ten that came back, sorted by cause

The distinction matters because each cause needs a different response from whoever is writing. Lumping them together as “blocked” is what makes a citation tool feel unreliable when it is being accurate.

One was stopped by a bot defence

HostKindWhat happened
www.mdpi.comopen accessanswers a browser, refuses the reader

This is the only case in twenty where a browser sees something a server-side reader is not allowed to see. It is also the only case where opening the page yourself changes the outcome — see what to do with the ten.

Four refuse everyone from this address

HostKindWhat happened
www.sciencedirect.compublisherrefuses both — HTTP 403 from a data centre address
papers.ssrn.compreprintrefuses both — HTTP 403 from a data centre address
eur-lex.europa.euofficialrefuses both — HTTP 202 from a data centre address
www.oecd.orgofficialrefuses both — HTTP 403 from a data centre address

These answered 403 to a browser user agent as readily as to a reader. The common factor is not the client but the network: requests from a data centre are refused whatever they claim to be. From a home connection the same pages open normally. That is worth stating plainly, because it is the one result here that would look different measured from a different place.

Five answered in full and had nothing to declare

HostKindWhat happened
www.ssoar.inforepositoryanswers in full, declares no citation data
zenodo.orgrepositoryanswers in full, declares no citation data
www.statistik.atofficialanswers in full, declares no citation data
www.wko.atgrey litanswers in full, declares no citation data
www.derstandard.atnewsanswers in full, declares no citation data

Fifty to ninety kilobytes of perfectly readable HTML, no defence of any kind, and no citation_* metadata, no author, no publication date. A reference for these has to be written by a person who decides what the work is — a page of a statistics portal, an article in a newspaper, a software release on a repository. No amount of retrying changes that, and any tool that returns a tidy entry here has invented the missing half.

A record can carry a title and still not be one

Two of the five silent cases are the trap this measurement was worth doing for. The Zenodo record returns "kjswedberg/kjswedberg.github.io: First Release" with an author attached; the statistics portal returns "Forschung, Innovation, Digitalisierung" with STATISTIK AUSTRIA as author. Both look like results. Both come back with complete: false.

Anything reading the title field and skipping the flag will file both as sources. The lesson is not about this endpoint — it applies to every citation service: read the completeness flag, not the title. On this endpoint that check is one field:

if not record["complete"]:
    hand_back(url, record.get("warning") or "no citation data on the page")

A gap we should close on our side: in these five cases the warning field is empty. complete: false is correct and sufficient to act on, but a reason would be more useful than a silence, and it is noted as such.

What this does not settle

Running it yourself

One URL per line in, one importable .ris out, with the refusals named on stderr instead of half-imported. The recipes page has the same thing for Claude Code, Claude Desktop, Python and the browser.

while read -r u; do
  curl -sX POST https://provinglab.dev/mcp \
    -H 'content-type: application/json' \
    -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",
         \"params\":{\"name\":\"extract_citation\",\"arguments\":{\"url\":\"$u\"}}}" \
  | python3 -c 'import json,sys
d = json.loads(json.load(sys.stdin)["result"]["content"][0]["text"])
sys.stdout.write(d["ris"]) if d.get("complete") else \
  sys.stderr.write("hand back: " + d["url"] + "\n")'
done < reading-list.txt > literature.ris

Then Zotero → File → Import, or Citavi → Import → RIS. No key, no account. One caveat worth knowing: this site sits behind a filter that refuses the user agent Python's urllib sends by default. Set any user agent of your own and it answers normally.