MétaCan

The public API

Read-only, JSON, CORS-enabled, no key. Every endpoint is backed by the same functions the pages call — searchWorks() serves both /works and /api/v1/works — so the API cannot answer a different question from the page above it.

Base URL: https://metacan.xera.ac/api/v1

GET/api/v1/stats/summary

The frame in one object: total works, the no-affiliation and no-abstract counts, the four route marginals, and the screen's consensus histogram.

curl -sS https://metacan.xera.ac/api/v1/stats/summary | jq
GET/api/v1/works

Browse and search the whole frame. Full-text over titles, every filter the browse page offers, paginated.

The count is capped at 10,000. Counting 4.3M rows exactly costs seconds and nobody reads the number, so total_is_capped: true means “at least 10,000”, not “exactly 10,000”. Page through if you need more.
ParameterTypeMeaning
qstringFull-text search over titles (Postgres tsvector; terms are ANDed).
year_from, year_tointInclusive publication-year bounds.
langstringLanguage code, e.g. en, fr.
typestringWork type, e.g. article, preprint, dissertation.
fieldstringOpenAlex primary field, e.g. 'Medicine'.
routeaff | fund | venue | about | no_affRoute provenance: why the work is in the frame. no_aff returns the works with NO Canadian affiliation — the ones an affiliation-only frame never sees.
retracted1Only works OpenAlex flags as retracted.
no_abstract1Only works with no abstract. The screen finds half as much metaresearch here.
n_in0..3Screening consensus: how many of the three models called it metaresearch.
sortcited | year_desc | year_ascDefault: cited.
page, per_pageintper_page max 100, default 25.
# The works an affiliation-only frame would never have seen,
# most-cited first:
curl -sS "https://metacan.xera.ac/api/v1/works?route=no_aff&sort=cited&per_page=5" | jq '.results[] | {id, title, cited_by, routes}'

# French-language works with no abstract, published since 2015:
curl -sS "https://metacan.xera.ac/api/v1/works?lang=fr&no_abstract=1&year_from=2015&per_page=5" | jq

# Full-text search:
curl -sS "https://metacan.xera.ac/api/v1/works?q=reproducibility+crisis&per_page=3" | jq '.results[].title'
GET/api/v1/works/{id}

One work, every field, the routes that admitted it, its Retraction Watch state if any, and all three models' labels and reasons if it was screened.

ParameterTypeMeaning
abstract1Fetch the abstract live from OpenAlex and de-invert it. Off by default: abstracts are not in this database, so asking for one costs an upstream round-trip.
# A work, with its provenance:
curl -sS https://metacan.xera.ac/api/v1/works/W2342586781 | jq '{id, title, routes}'

# With the abstract fetched live from OpenAlex:
curl -sS "https://metacan.xera.ac/api/v1/works/W2342586781?abstract=1" | jq '.abstract'
GET/api/v1/screened

The 1,000 screened works with all three models' tiers, genres, confidences and reasons, plus the design weight.

The sample is stratified. Every record carries a weight (inverse selection probability). Any rate you compute from these rows without applying the weight is wrong.
ParameterTypeMeaning
contested_only1THE DISAGREEMENT DOSSIER: every work any model called metaresearch. This subset, not the base rate, is the project’s deliverable.
n_in0..3Exact consensus count.
stratumstringe.g. aff_core, about_only, french, venue_new, fund_new.
page, per_pageintper_page max 100.
# The disagreement dossier: the works that mark the field's boundary.
curl -sS "https://metacan.xera.ac/api/v1/screened?contested_only=1" | jq '.meta.summary'

# The works only ONE model called metaresearch:
curl -sS "https://metacan.xera.ac/api/v1/screened?n_in=1" \
  | jq '.results[] | {title, opus: .opus.tier, gpt: .gpt.tier, grok: .grok.tier}'
GET/api/v1/stats/by-route

The four routes: marginals, and the exact route combinations.

The routes overlap — a work can be admitted by several — so marginals sums to more than the frame. combinations counts each work once and sums to the total.
curl -sS https://metacan.xera.ac/api/v1/stats/by-route | jq '{no_aff: .meta.no_aff, marginals, combinations: .combinations[:5]}'
GET/api/v1/stats/by-year

Works per year, with the no-affiliation and no-abstract counts alongside, because both gaps move over time.

curl -sS https://metacan.xera.ac/api/v1/stats/by-year | jq '.results[-5:]'
GET/api/v1/stats/by-field

The field breakdown, plus languages, the abstract gap by type, top venues, top funders, and the four-state retraction record — everything the analytics page draws, in one call.

curl -sS https://metacan.xera.ac/api/v1/stats/by-field | jq '.retraction_states'
GET/api/v1/findings

All 22 findings, served verbatim from the file the pilot scripts write. Every number quoted anywhere on this site comes from here.

curl -sS https://metacan.xera.ac/api/v1/findings | jq '.findings.three_model_screen.headline'

Notes

  • CORS is open (*). This is a public CC-BY research dataset; the point of publishing it is that you can query it from your own page without proxying.
  • Caching. Responses carry s-maxage=3600. The frame is a pinned snapshot: it does not change between deploys, so a stale aggregate is not a risk and re-scanning 4.3M rows per request would be.
  • No key, no rate limit — but it is one small server. Be reasonable, and if you need the whole frame, take the repository and rebuild it locally rather than paginating four million rows out of this box.
  • Licence. Data CC-BY-4.0, code MIT. Cite OpenAlex and Retraction Watch as the upstream sources.