Life of Research
Appearance

Developers and AI agents

Every public record in Life of Research is available to software without an account or API key: a JSON API for applications, OAI-PMH for harvesters, a Model Context Protocol server for AI assistants, and feeds for crawlers. Metadata is free to reuse for any purpose, including text and data mining.

Machine interfaces
InterfaceEndpointBest for
JSON API/api/recordsSearch and fetch records from your own code
MCP server/mcpLetting an AI assistant search, read and cite records
OAI-PMH 2.0/oai2Bulk metadata harvesting by aggregators
Feeds and descriptions/feed.xml, /sitemap.xml, /api/openapi.jsonChange monitoring, crawling and client generation

JSON API

The JSON API returns published records as JSON with open CORS (Access-Control-Allow-Origin: *), so it works from a browser as well as from a server. There is no authentication.

RequestReturns
GET /api/recordsPublished records, newest deposit first, 20 per page: {"total", "page", "per_page", "records": [...]}
GET /api/records?q=words&page=nSearch titles, abstracts, keywords and author names
GET /api/records/idOne record plus its files, each with a download_url; 404 if it does not exist or was withdrawn

Each record carries id, identifier (such as LOR-00042), url, title, abstract, authors (name, affiliation, ORCID), type, license, keywords, publication_date, doi, version, views and downloads. The full schema is in the OpenAPI 3.1 description.

curl

curl -s 'https://lifeofresearch.com/api/records?q=climate&page=1'
curl -s 'https://lifeofresearch.com/api/records/111'

Python (standard library only)

import json
import urllib.parse
import urllib.request

BASE = "https://lifeofresearch.com"
HEADERS = {"User-Agent": "my-project/1.0 (mailto:you@example.org)"}

def get(path, **params):
    url = BASE + path + ("?" + urllib.parse.urlencode(params) if params else "")
    with urllib.request.urlopen(urllib.request.Request(url, headers=HEADERS), timeout=30) as r:
        return json.load(r)

page = get("/api/records", q="climate", page=1)
print(page["total"], "matching records")
for rec in page["records"]:
    print(rec["identifier"], rec["title"], rec["url"])

record = get("/api/records/111")
for f in record["files"]:
    print(f["name"], f["download_url"])

MCP server for AI assistants

The Model Context Protocol server at https://lifeofresearch.com/mcp lets an AI assistant search the repository, read records and produce citations itself. It is read-only, needs no account, and uses the Streamable HTTP transport with JSON-RPC 2.0.

  • Protocol versions: 2026-07-28 (stateless, per-request _meta, server/discover), and the initialize handshake of 2025-11-25, 2025-06-18, 2025-03-26 for older clients.
  • Server card: /mcp/server-card describes the endpoint before connecting. It follows the draft server-card proposal (SEP-2127), an experimental extension that may still change.
  • Sessions: none. GET and DELETE on /mcp return 405; every request stands alone.

Connect a client

Most MCP clients only need the URL: add a remote server of type Streamable HTTP (sometimes labelled “HTTP”) pointing at https://lifeofresearch.com/mcp. With Claude Code, for example:

claude mcp add --transport http life-of-research https://lifeofresearch.com/mcp

To explore the tools interactively, run the MCP Inspector (npx @modelcontextprotocol/inspector) and connect it to the same URL.

Tools

ToolWhat it doesArguments
search_records Search published records in the Life of Research open-access repository. The query matches titles, abstracts, keywords and author names. Filter by record type and publication year. Results are newest deposit first. query, type, year_from, year_to, limit, offset
get_record Full metadata for one published record: title, authors with affiliations and ORCID iDs, abstract, keywords, dates, license, DOI, journal details, files with download links, view and download counts, and an APA citation. id (required)
get_citation A ready-to-use citation for a published record. Use style for formatted text (apa, mla, chicago, harvard, ieee, vancouver) or format for a reference-manager export (bibtex, ris, csl-json). Defaults to APA. id (required), style, format
list_recent The most recently deposited published records, newest first, optionally of one type. limit, type
list_subjects The most common subject keywords across published records, with record counts, plus the number of records of each type. Useful for seeing what the repository covers before searching. limit

Every tool returns both structuredContent (matching its outputSchema) and the same JSON as text. Invalid arguments come back as a tool result with isError: true and a message saying what to fix; an unknown tool name is a JSON-RPC -32602 error.

Raw requests (protocol 2026-07-28)

Each request carries its protocol version and client capabilities in params._meta, mirrored in the MCP-Protocol-Version, Mcp-Method and (for tools/call) Mcp-Name headers. Mismatched headers are rejected with 400 and error -32020; an unsupported version with -32022, which lists the versions this server accepts.

curl -s https://lifeofresearch.com/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: server/discover' \
  -d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{},"io.modelcontextprotocol/clientInfo":{"name":"curl","version":"1.0"}}}}'
curl -s https://lifeofresearch.com/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: tools/call' \
  -H 'Mcp-Name: search_records' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search_records","arguments":{"query":"open access","limit":3},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{},"io.modelcontextprotocol/clientInfo":{"name":"curl","version":"1.0"}}}}'

Older clients (initialize handshake)

curl -s https://lifeofresearch.com/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'

Python

import json
import urllib.request

URL = "https://lifeofresearch.com/mcp"
VERSION = "2026-07-28"

def mcp(method, params=None, request_id=1):
    params = dict(params or {})
    params["_meta"] = {
        "io.modelcontextprotocol/protocolVersion": VERSION,
        "io.modelcontextprotocol/clientCapabilities": {},
        "io.modelcontextprotocol/clientInfo": {"name": "my-script", "version": "1.0"},
    }
    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json, text/event-stream",
        "MCP-Protocol-Version": VERSION,
        "Mcp-Method": method,
    }
    if method == "tools/call":
        headers["Mcp-Name"] = params["name"]
    body = json.dumps({"jsonrpc": "2.0", "id": request_id, "method": method, "params": params}).encode()
    with urllib.request.urlopen(urllib.request.Request(URL, body, headers), timeout=30) as r:
        return json.load(r)["result"]

hits = mcp("tools/call", {"name": "search_records", "arguments": {"query": "open access", "limit": 3}})
for rec in hits["structuredContent"]["records"]:
    cite = mcp("tools/call", {"name": "get_citation", "arguments": {"id": rec["identifier"], "format": "bibtex"}}, 2)
    print(cite["structuredContent"]["citation"])

OAI-PMH for harvesters

The OAI-PMH 2.0 endpoint at https://lifeofresearch.com/oai2 (also /oai) is how academic aggregators collect metadata in bulk. GET and POST behave identically, as the protocol requires.

  • Verbs: Identify, ListMetadataFormats, ListSets, ListIdentifiers, ListRecords, GetRecord.
  • Formats: oai_dc (Dublin Core) and oai_datacite (DataCite metadata).
  • Sets: type:article, type:dataset and one per record type, plus openaire for the scholarly types.
  • Identifiers: oai:lifeofresearch.com:id. Withdrawn records are reported as deleted (transient).
  • Paging: 100 records per response, continued with resumptionToken; from and until take UTC dates or date-times.
curl -s 'https://lifeofresearch.com/oai2?verb=Identify'
curl -s 'https://lifeofresearch.com/oai2?verb=ListRecords&metadataPrefix=oai_dc&set=type:dataset'
curl -s 'https://lifeofresearch.com/oai2?verb=GetRecord&metadataPrefix=oai_datacite&identifier=oai:lifeofresearch.com:111'

Python harvest loop

import urllib.parse
import urllib.request
import xml.etree.ElementTree as ET

BASE = "https://lifeofresearch.com/oai2"
NS = {"oai": "http://www.openarchives.org/OAI/2.0/", "dc": "http://purl.org/dc/elements/1.1/"}

params = {"verb": "ListRecords", "metadataPrefix": "oai_dc"}
while True:
    with urllib.request.urlopen(BASE + "?" + urllib.parse.urlencode(params), timeout=60) as r:
        root = ET.parse(r).getroot()
    for rec in root.iterfind(".//oai:record", NS):
        ident = rec.findtext("oai:header/oai:identifier", namespaces=NS)
        title = rec.findtext(".//dc:title", namespaces=NS)
        print(ident, title)
    token = root.findtext(".//oai:resumptionToken", namespaces=NS)
    if not token:
        break
    params = {"verb": "ListRecords", "resumptionToken": token}

Feeds, descriptions and discovery files

Record pages also carry machine-readable metadata: Highwire Press citation_* and Dublin Core meta tags, schema.org JSON-LD, and Signposting Link headers (cite-as, author, license, item, describedby) pointing to the JSON and OAI-PMH descriptions of the same record.

Rate limits and etiquette

InterfaceLimit
MCP server (/mcp)120 requests per minute per IP address. Above that the server answers 429 with a Retry-After header.
JSON API, OAI-PMHNo fixed limit today. Please keep to about one request per second and avoid fetching the same page twice in a short time.
Sitemap, Atom feed, llms.txt, statistics, OpenAPINo fixed limit. These send an ETag: repeat the request with If-None-Match and an unchanged file costs a 304 Not Modified with no body.

Send a descriptive User-Agent with a contact address, so we can reach you before blocking anything. For a full copy of the metadata, harvest OAI-PMH rather than paging through search results.

Reusing the data

Record metadata (titles, authors, abstracts, keywords, dates and identifiers) may be reused for any purpose, including indexing and text and data mining. The files attached to each record are covered by the license the depositor chose, shown on the record page and in the API's license field. When you display a record, link to its permanent URL or DOI.

Questions, bugs or a feature you need: contact@lifeofresearch.com.