Skip to content

Papers feed

A signed delta feed of open-access papers: licence, retraction notices, a text fingerprint and the open abstract, for retrieval systems.

On this page

A signed list of the open-access papers naulon indexes, for retrieval systems that want to know, before they serve a paper, what they may do with it and whether the scholarly record still stands behind it. Each row gives the paper's licence, its retraction and correction notices, a fingerprint of its text and, where the licence allows, its abstract. The feed is a delta: you sync it once, then ask only for what changed.

It's free and needs no key.

GET https://gate.naulon.app/_naulon/v1/feed/papers

Paging

A page holds up to 500 rows, oldest change first. Each page carries next, the cursor just after its last row, and more, which is true when the page was full and more rows are already waiting. Pass next back as cursor to continue. When more is false you have everything for now.

curl "https://gate.naulon.app/_naulon/v1/feed/papers"
curl "https://gate.naulon.app/_naulon/v1/feed/papers?cursor=MjAyNi0xMC0xNFQxNjoyODo0Mi42MTI1Mjcr..."

Keep the last next you saw. Asking with it later returns every row that changed since, and nothing else; an empty page hands the same cursor back. To start from a date instead, use since:

curl "https://gate.naulon.app/_naulon/v1/feed/papers?since=2026-10-21T00:00:00Z"

limit lowers the page size. A cursor wins over since when you send both.

A row appears again whenever anything it states changes: a new notice, a licence change, a new text version, a better identifier. Dedupe on id, which never changes. key can change: a paper first known by its PMCID gets its DOI as key once the DOI is known.

The feed holds back rows that changed in the last 60 seconds (lag_seconds), so a change written while you were paging is never skipped. It reaches you on a later page instead.

A row

{
  "kind": "paper",
  "id": "b6a84cf9-bcde-414c-8feb-1b76c4ff9020",
  "key": "doi:10.1038/s41586-021-03819-2",
  "url": "https://pmc.ncbi.nlm.nih.gov/articles/PMC8371605/",
  "title": "Highly accurate protein structure prediction with AlphaFold",
  "authors": [{ "name": "John Jumper" }, { "name": "Richard Evans" }],
  "year": 2021,
  "venue": "Nature",
  "changed_at": "2026-10-14T16:28:42.612527+00:00",
  "state": "live",
  "terms": { "licence_code": "CC BY", "licence_class": "full", "licence_at": "https://pmc.ncbi.nlm.nih.gov/articles/PMC8371605/" },
  "commitment": { "text_root": "28bd99e0d37d329a365bb9cc808248667abd5568caaabb5d0ea40ffde95b298c", "leaf_count": 204, "pmc_version": 2 },
  "integrity": { "status": "checked", "checked_at": "2026-10-14T09:38:29.775+00:00", "retracted": false, "notices": [] },
  "abstract": {
    "text": "Proteins are essential to life, and understanding their structure can facilitate...",
    "source": "crossref",
    "truncated": false,
    "attribution": {
      "title": "Highly accurate protein structure prediction with AlphaFold",
      "authors": ["John Jumper", "Richard Evans"],
      "doi_url": "https://doi.org/10.1038/s41586-021-03819-2",
      "licence_at": "https://pmc.ncbi.nlm.nih.gov/articles/PMC8371605/"
    }
  }
}
  • url is the paper's free copy at PubMed Central. The full text is there, under the licence in terms. The feed never carries body text.
  • terms.licence_code is the licence PMC states for the copy. licence_class is full when the licence allows reuse of the whole text, and quotes when only quotation is safe. The code carries no version number, so the full licence is read at licence_at.
  • commitment.text_root is a Merkle root over the paper's sentences. Any passage naulon later proves to you from this paper verifies against it, so you can tell whether two copies of a paper are the same text.
  • integrity.status is unchecked until naulon has looked the paper up for notices. An unchecked paper is not a clean one. notices lists retractions, corrections, expressions of concern and withdrawals, each with its notice DOI, date and source.
  • abstract appears only when the paper's published version carries an open licence. If you show it, show the attribution beside it. truncated is true when the abstract was cut at 10,000 characters.

Removals

A paper that leaves the feed comes back one last time with state: "removed" and a reason. Drop it, or mark it, on your side:

reason What happened
retracted the paper was retracted, by its publisher's notice or on PMC
withdrawn the paper was withdrawn
merged two records were the same paper; merged_into names the one that remains, which is in the feed
gone PMC no longer serves it openly, or its licence narrowed
unlisted it left the list of papers this feed covers

A removed row carries its notices, and no abstract or text fingerprint.

Checking a page

Every page is signed. The signature is in the Naulon-Feed-Signature response header, a JSON Web Signature with a detached payload (RFC 7515, appendix F) over the exact bytes of the response body. The keys are at:

GET https://gate.naulon.app/.well-known/naulon-feed-jwks.json

Store the body and the header together. The pair proves what naulon stated about each paper and when, including "checked, no notice known", without anyone having to trust your copy. A key that is retired stays in the key set, so an old page keeps verifying.

To check one in Node:

import { createPublicKey, verify } from "node:crypto";

const res = await fetch("https://gate.naulon.app/_naulon/v1/feed/papers");
const body = Buffer.from(await res.arrayBuffer());
const [header, , signature] = res.headers.get("naulon-feed-signature").split(".");
const { kid, alg, typ } = JSON.parse(Buffer.from(header, "base64url"));
if (alg !== "EdDSA" || typ !== "naulon-feed+jws") throw new Error("not a feed signature");
const { keys } = await (await fetch("https://gate.naulon.app/.well-known/naulon-feed-jwks.json")).json();
const key = createPublicKey({ key: keys.find((k) => k.kid === kid), format: "jwk" });
const ok = verify(null, Buffer.from(`${header}.${body.toString("base64url")}`), key, Buffer.from(signature, "base64url"));

The page

Besides rows and next, each page carries:

  • integrity_as_of: when naulon last ran its notice checks. Read each paper's own integrity.checked_at for when that paper was looked up; an unchecked paper with no notices is not a clean one.
  • credit: the sources to acknowledge. Paper copies come from PubMed Central at the U.S. National Library of Medicine; notices come from Crossref (including Retraction Watch data), publishers and OpenAlex.
  • licence: naulon's own statements in the feed are CC0. Abstracts keep their papers' licences.
  • how_to_buy: where to buy passages proven against text_root, over HTTP with x402 or as the naulon_retrieve MCP tool.
  • links.verify: the page that opens a receipt. A retrieve answer already gives each source's proofUrl filled in; the template is for a record id you stored on its own.

Limits

Pages are cached at the edge for up to a minute, and the first page for 30 seconds. One address may ask the origin 60 times a minute; past that the feed answers 429 with retry-after. A first full sync is more pages than that, so wait the seconds retry-after names and carry on from the same cursor. A malformed cursor, since or limit gets 400 naming the parameter, and so does site, which only the articles feed takes.