# JATS partner API contract

This dev contract was verified on 16 September 2026 against the 100-article JNIS collection and the 25-article OA collection. The endpoint is `https://dev.bmj-content-hub.com`; use the existing dev credentials. These changes have not been promoted to live or the original evaluator.

## Search and browse

Use `GET /v1/articles` with an optional `query`. The lexical query searches normalized title, abstract, body, and publisher keywords. Bibliography and identifiable inline bibliographic citations are excluded, so a reference-only term cannot create a hit. Unquoted input is term-based, quoted text remains a phrase unit. Uppercase Boolean operators (`AND`, `OR`, `NOT`, `NEAR`) are rejected; there is no Boolean parser. `rank=bm25` is the available default; `rank=vector` and `rank=hybrid` are reserved, with `hybrid` returning `501` and making no vector call. The starting SQLite FTS5 profile is `k1=1.2`, `b=.75`, field weights title 5, abstract 2, body 1, keywords 3; positive BM25 score is used, with stable ID tie-breaking. Corpus-global index statistics are shared across customer views and filtering happens before pagination. `order` accepts `desc` or `asc`. Apply `collection_id`, comma-separated `journal_ids`, optional literal `author` and `keyword` filters (maximum 128 characters each), and inclusive actual publication date bounds. Author matching is normalized literal word-boundary substring/token phrase matching without stemming, fuzzy matching, synonym expansion, or identity guessing. `page_size` is 1-300 and defaults to 100. Customer policy limits remain authorization/quota controls.

The default response is thin. `include=frontmatter` adds the complete abstract, authors, keywords, journal, licence, source, and version metadata flattened onto each hit. `include_unmatched=true` explicitly adds zero-score articles from the permitted corpus; with a query, the default is matched rows only. `total` is the complete logical result-set count; `matched_total` is query matches across the result set, and `returned` is the rows on the current page. Browse hits omit `score`.

Use the opaque `next_cursor` exactly as returned. It is limited to 2048 characters and bound to customer scope, query and filters, index, and active release revision. A changed corpus or release invalidates the cursor with `409`; restart the request. Do not construct offsets or decode cursors.

Example: `GET /v1/articles?query=ischaemic&author=Smith&published_from=2020-01-01&published_to=2024-12-31&include=frontmatter`.

These routes do not apply automatic typo correction or quorum relaxation. A term present only in bibliography cannot create a hit.

Each hit exposes `identifiers` with `content_id`, `doi`, `publisher_id`, and `publisher_url`; the stable internal content ID is retained. Exact DOI lookup accepts `doi:10.1136/...`, `https://doi.org/...`, the BMJ DOI lookup URL, and PMC publisher IDs/source URLs, using only the item's own metadata and never body or reference text. URL query values must be URL-encoded. The same aliases are accepted by fetch and frontmatter lookup.

## Front matter and complete content

`GET /v1/frontmatter?ids=a,b,c` accepts at most 50 IDs, preserves input order, and omits unknown or out-of-scope IDs. Its collection response uses `items`, `total`, `next_cursor`, and `snapshot`. Without `ids`, it is a date-descending paged browse with the same filters and cursor semantics. `GET /v1/frontmatter/{id}` returns one scoped metadata record.

`GET /v1/articles/{id}` returns the complete article. The default representation is JATS XML; `format=jats|json|markdown` selects the representation, and `version_id` is a guard for the latest permitted version; historical version retrieval is not implemented. A stale guard returns `404`, so search again before retrying. Large responses return explicit `413` rather than truncating content.

## MCP tools

- `search(query)`: returns structured `id`, `title`, and `url` results; each result's content includes the complete abstract.
- `fetch(id)`: returns the whole article, metadata, and references with `id`, `title`, `text`, and `url`.

MCP calls inherit customer authentication, content entitlements, corpus scope, audit, and release/version checks. They are available on the verified dev deployment.

The five-hit MCP/search presentation remains the starting policy pending measurement. Page size 100 (maximum 300) and ID batches of 50 are starting resource safeguards, not a tariff. BM25 statistics include all indexed version rows and are comparable only within the same index revision. Search observability records D1 rows read, execution time, response bytes, item count, and latency. Content licence units approximate response size, not AI model tokens; future AI usage is measured separately.

## Compatibility and rollout

`POST /v1/articles/search` and `GET /v1/articles/{id}/content` remain legacy compatibility routes. Existing nine-article vector and AI paths are unchanged. The JATS pilot uses the canonical article pipeline and a batch-read metadata projection; it does not import or vectorise the full corpus. Publisher metadata (authors, DOI, title, dates, journal display value, journal ID/code, abstract, keywords, licence text, source, version) is core; externally sourced metrics, classifications, and additional provenance are future sidecars. Semantic retrieval, historical-version delivery and production SLAs remain future work. Cursor revision checks are already implemented.
