Tutorial5 min
Search the archive
- Endpoint
GET /reports/search- Auth
X-API-Key- Query
q, cursor, pageSize, filters- Returns
200 { data, pageSize, nextCursor }
Before you start
A Core API base URL
Pick the environment you are building against. Base URLs has the details.
- ProductionProvided at onboardingMutual TLS required
- Sandbox
https://staging-api.emergentriskinternational.com/api/v1Same contract as production - Local
http://127.0.0.1:3001/api/v1apps/backend on your machine
An API Key
Organization-scoped, sent in the
X-API-Keyheader and issued by ERI support. See Authentication.The feed tutorial
Paging and filters work exactly as in Ingest the report feed. This guide covers what search adds.
Step 1: Send a query
Put the search phrase in
q, 1 to 200 characters. It matches whole words in the title and body, ignoring case and word endings:bulletinsfinds a report titled "Cyber Threat Bulletin".qreads the text only, not the tags. To match a region, topic or other tag, use the filters in step 3.Results come back newest first, like the feed, not ranked by relevance.
No match is a
200with an emptydataarray.Terminalexport CORE_API_BASE_URL="http://127.0.0.1:3001/api/v1"export ERI_API_KEY="<raw API Key from bootstrap or ERI support>"- cURL
- Python
- Node.js
curl -G "$CORE_API_BASE_URL/reports/search" \-H "X-API-Key: $ERI_API_KEY" \--data-urlencode "q=cyber threat"import os, requestsresponse = requests.get(f"{os.environ['CORE_API_BASE_URL']}/reports/search",headers={"X-API-Key": os.environ["ERI_API_KEY"]},params={"q": "cyber threat"},)matches = response.json()["data"]const url = new URL(`${process.env.CORE_API_BASE_URL}/reports/search`);url.searchParams.set("q", "cyber threat");const response = await fetch(url, {headers: { "X-API-Key": process.env.ERI_API_KEY },});const { data: matches } = await response.json();200 OK{"data": [{"id": "01a04100-0000-7000-8000-000000000004","title": "Baltic Corridor - Cyber Threat Bulletin","publicationDate": "2026-08-29T10:53:16.189Z","region": "Europe","reportType": "Rapid Reaction Brief","regions": ["Europe"],"countries": ["Estonia", "Latvia", "Lithuania"],"topics": ["Cyberthreat"],"sectors": ["Maritime"]}],"pageSize": 50,"nextCursor": null}Step 2: Refine the phrase
qunderstands a small search syntax:- Separate words must all appear:
cyber threat. - Quotes keep words together and in order:
"cyber threat". oraccepts either side:cyber or political.- A leading minus excludes a word:
bulletin -baltic.
curl -G "$CORE_API_BASE_URL/reports/search" \-H "X-API-Key: $ERI_API_KEY" \--data-urlencode 'q="supply chain" -china'- Separate words must all appear:
Step 3: Combine with filters
Every feed filter works here too:
contentType,region,country,topicandsector. They narrow the matches the same way, OR within a parameter and AND across them.To page, send
nextCursorback ascursor, with the sameqand filters on every request.- cURL
- Python
- Node.js
curl -G "$CORE_API_BASE_URL/reports/search" \-H "X-API-Key: $ERI_API_KEY" \--data-urlencode "q=baltic corridor" \--data-urlencode "region=Europe"def search(q, **filters):params = {"q": q, **filters}while True:response = requests.get(f"{os.environ['CORE_API_BASE_URL']}/reports/search",headers={"X-API-Key": os.environ["ERI_API_KEY"]},params=params,)response.raise_for_status()page = response.json()yield from page["data"]if page["nextCursor"] is None:returnparams["cursor"] = page["nextCursor"]for report in search("baltic corridor", region=["Europe"]):print(report["title"])async function* search(q, filters = {}) {let cursor = null;do {const url = new URL(`${process.env.CORE_API_BASE_URL}/reports/search`);url.searchParams.set("q", q);for (const [name, values] of Object.entries(filters)) {for (const value of values) url.searchParams.append(name, value);}if (cursor) url.searchParams.set("cursor", cursor);const response = await fetch(url, {headers: { "X-API-Key": process.env.ERI_API_KEY },});if (!response.ok) throw new Error(`GET /reports/search failed: ${response.status}`);const page = await response.json();yield* page.data;cursor = page.nextCursor;} while (cursor !== null);}for await (const report of search("baltic corridor", { region: ["Europe"] })) {console.log(report.title);}Step 4: Handle the response
Search answers with the feed's envelope and errors, plus a
400whenqis missing, empty or too long. Pick a status to see the exact body.200OK- When
- The matching reports, newest first. No match is a 200 with an empty data array, not a 404.
- What to do
- Store the matches, then request the next page while nextCursor is not null.
Response body{"data": [],"pageSize": 50,"nextCursor": null}
You can find any report
Search and the feed share one page shape, so the same storage code handles both.