---
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 **six academic sources at once** and get a single merged, deduplicated list:

- `arxiv` — the ScholarXIV arXiv corpus
- `openalex` — OpenAlex
- `pubmed` — PubMed / NCBI
- `semantic_scholar` — Semantic Scholar
- `crossref` — Crossref
- `chinarxiv` — ChinaXiv

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 six | 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).
