---
title: "Federated Search"
description: "Search arXiv, OpenAlex, PubMed, Semantic Scholar, Crossref, and ChinaXiv at once. Merged, deduplicated results with per-source pagination."
canonical_url: "https://scholarxiv.com/developers/docs/papers-api/federated-search"
markdown_url: "https://scholarxiv.com/developers/docs/papers-api/federated-search.md"
---

> For the complete documentation index, see [llms.txt](/llms.txt).

# Federated Search

Query **eleven academic sources at once** and get a single merged, deduplicated list:

- `arxiv` — the ScholarXIV arXiv corpus
- `openalex` — OpenAlex
- `pubmed` — PubMed / NCBI
- `europepmc` — Europe PMC
- `medrxiv` — medRxiv
- `biorxiv` — bioRxiv
- `clinicaltrials` — ClinicalTrials.gov
- `semantic_scholar` — Semantic Scholar
- `crossref` — Crossref
- `chinarxiv` — ChinaXiv
- `chemrxiv` — ChemRxiv

Sources are fetched in parallel and duplicates (same DOI, or same normalized title) are collapsed into one paper that records every source it was found in.

> [!IMPORTANT]
> Federated search requires the **Go plan or above**. Requests from free-plan keys return `403`. See the standard single-source [Search](/developers/docs/papers-api/search) for arXiv-only access on any plan.

## Endpoint

```http
GET  /api/v1/papers/federated/search?q=your+query&page=0&limit=10
POST /api/v1/papers/federated/search
```

Both methods take the same parameters and return the same shape.

### Parameters

| Param     | Required | Default    | Description                                                                        |
| --------- | -------- | ---------- | ---------------------------------------------------------------------------------- |
| `q`       | Yes      | —          | Search query (GET query param, or `q`/`query` in the POST body)                    |
| `page`    | No       | 0          | Zero-based page index (applied **per source**)                                     |
| `limit`   | No       | 20         | Results **per source**, 1–50                                                       |
| `sources` | No       | all eleven | Restrict to specific sources. GET: comma list (`arxiv,pubmed`). POST: string array |

**POST body example:**

```json title="body.json"
{
	"q": "retrieval augmented generation",
	"page": 0,
	"limit": 10,
	"sources": ["arxiv", "openalex", "semantic_scholar"]
}
```

## Per-source pagination

Paging is **per source**, not global. A single page requests up to `limit` results from _each_ selected source, so a page can return up to `limit × (number of sources)` papers before deduplication.

- `page` and `limit` are applied independently to every source (offset = `page × limit`).
- Each source reports its own `hasMore` in `bySource`.
- The top-level `pagination.hasMore` is `true` when **any** source still has more results.

Because sources are independent, some may run out of results before others on deep pages — that is expected and is surfaced honestly per source in `bySource`.

## Response

```ts title="response.ts"
{
  data: Paper[],                                  // merged + deduped across sources for this page
  bySource: {
    [source: string]: { count: number, hasMore: boolean }
  },
  pagination: { page: number, limit: number, hasMore: boolean, nextPage: number | null }
}
```

Each paper carries a `source` (and a `sources` array when found in more than one), plus `title`, `authors`, `summary`, `year`, `doi`, `pdfLink`, `url`, and `citationCount` where available.

## Pagination loop

Drive the loop off `pagination.nextPage` — never guess page numbers.

```ts title="paginate.ts"
async function fetchAll(query: string, key: string) {
	const all: unknown[] = [];
	let page: number | null = 0;

	while (page !== null) {
		const res = await fetch(
			`https://scholarxiv.com/api/v1/papers/federated/search?q=${encodeURIComponent(query)}&page=${page}&limit=25`,
			{ headers: { Authorization: `Bearer ${key}` } }
		);
		if (!res.ok) throw new Error(`HTTP ${res.status}`);
		const body = await res.json();
		all.push(...body.data);
		page = body.pagination.nextPage; // null when every source is exhausted
	}
	return all;
}
```

## Empty results

A successful search with no matches returns HTTP 200 with an empty `data` array — normal, not an error.

```json
{
	"data": [],
	"bySource": {},
	"pagination": { "page": 0, "limit": 20, "hasMore": false, "nextPage": null }
}
```

## Notes

- `limit` is capped at **50 per source** to stay polite to upstream providers, even though your plan's overall result window is larger (see [Rate Limits & Quotas](/developers/docs/papers-api/limits)).
- A single flaky source never fails the whole request — it simply reports `{ count: 0, hasMore: false }` in `bySource`.
- Prefer the single-source [Search](/developers/docs/papers-api/search) endpoint when you only need arXiv; it is faster, available on every plan, and supports fielded/advanced filters.

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
