Polling a search endpoint for "anything newer than the last thing I saw" has two failure modes. Articles are not indexed in the order they were published: a story posted at 09:00 can arrive at 09:40, after you already moved your "newer than" mark past it, and you never see it. And if you widen the window to be safe, you see the same statements again and again.
/v2/feed removes both problems. It returns statements in the order they entered the API and hands you a cursor
that marks your position in that order. Pass the cursor back and you get exactly what arrived since: nothing missed,
nothing repeated.
The first call
Without a cursor the feed starts 24 hours back. Filters are the same as on /v2/statements; here, only important
statements:
You need a RapidAPI key to run the examples: the free plan is enough.
curl -G https://finance-pulse.p.rapidapi.com/v2/feed \
--data-urlencode "importance_min=high" --data-urlencode "limit=5" \
-H "X-RapidAPI-Key: YOUR_RAPIDAPI_KEY" -H "X-RapidAPI-Host: finance-pulse.p.rapidapi.com"
{
"data": [
{
"statement": "Kenon Holdings Ltd. entered into an agreement to acquire 25% equity interest in Vicinity District Energy for approximately $450 million.",
"published_at": "2026-09-30T10:09:00Z",
"indexed_at": "2026-09-30T11:33:18.118855Z",
"source_domain": "prnewswire.com",
"importance": "high",
"topic_name": "Kenon acquires 25% stake in Vicinity District Energy"
},
{
"statement": "Gold Circuit Electronics plans to bring in Amazon as a strategic investor through a private placement",
"published_at": "2026-09-30T10:06:28Z",
"indexed_at": "2026-09-30T11:34:17.428960Z",
"source_domain": "digitimes.com",
"importance": "high",
"topic_name": "Gold Circuit Electronics acquires Amazon equity for AI server PCBs"
},
… 3 more
],
"next_cursor": "WzI2MDU0OSwiNTVkYTE5ZDItZDE4My01ZTc5LWE5M2YtNzRlOTY4YWEwMjQzIl0"
}
Statements come oldest first by indexed_at, the moment they entered the API. Compare the two timestamps on each:
published_at is often minutes, sometimes hours, earlier. A loop keyed on publication time would have to guess how
long to wait for late articles; the feed simply delivers each statement when it arrives.
The loop
Store next_cursor after every call and pass it on the next one. Persisting it to disk means a restart resumes where
it stopped:
import json, pathlib, time, requests
API = "https://finance-pulse.p.rapidapi.com"
HEADERS = {"X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY", "X-RapidAPI-Host": "finance-pulse.p.rapidapi.com"}
STATE, LOG = pathlib.Path("feed.cursor"), pathlib.Path("statements.jsonl")
params = {"sector": "Energy", "importance_min": "high", "limit": 100}
while True:
if STATE.exists():
params["cursor"] = STATE.read_text()
page = requests.get(f"{API}/v2/feed", headers=HEADERS, params=params, timeout=30).json()
with LOG.open("a") as log:
for s in page["data"]:
log.write(json.dumps(s) + "\n")
print("ALERT", s["published_at"][:16], s["statement"], f"({s['source_domain']})")
STATE.write_text(page["next_cursor"])
if len(page["data"]) < params["limit"]:
time.sleep(150) # caught up: new data arrives about every 2.5 minutes
import { readFileSync, writeFileSync, appendFileSync, existsSync } from "node:fs";
const API = "https://finance-pulse.p.rapidapi.com";
const HEADERS = { "X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY", "X-RapidAPI-Host": "finance-pulse.p.rapidapi.com" };
const params = new URLSearchParams({ sector: "Energy", importance_min: "high", limit: "100" });
for (;;) {
if (existsSync("feed.cursor")) params.set("cursor", readFileSync("feed.cursor", "utf8"));
const page = await (await fetch(`${API}/v2/feed?${params}`, { headers: HEADERS })).json();
for (const s of page.data) {
appendFileSync("statements.jsonl", JSON.stringify(s) + "\n");
console.log("ALERT", s.published_at.slice(0, 16), s.statement, `(${s.source_domain})`);
}
writeFileSync("feed.cursor", page.next_cursor);
if (page.data.length < 100) await new Promise((r) => setTimeout(r, 150_000));
}
While catching up, full pages come back immediately; once you are current, the loop sleeps between polls. The Energy filter's first page, as the loop prints it:
ALERT 2026-09-30T10:07 BNZ selected Sitetracker to support scalable multitechnology renewable energy growth in Southern Europe (businesswire.com)
ALERT 2026-09-30T10:06 Inflação acelera em setembro para 3,6% puxada pelos combustíveis (jornaldenegocios.pt)
ALERT 2026-09-30T10:00 GasEntec is selected to supply regasification modules for an LNG carrier-to-FSRU conversion project with EXMAR (businesswire.com)
ALERT 2026-09-30T11:31 Rezolv secured a €561 million loan for a solar park in Romania (reuters.com)
ALERT 2026-09-30T09:31 The Italian business ministry announced that average fuel prices are down following price caps set by Eni, IP, and Q8. (ansa.it)
An empty page is normal
When nothing new matched your filters, the feed answers "data": [] with the same cursor you sent. That is not an
error: keep it and poll again later. A new snapshot is built about every 2.5 minutes, so polling faster only returns
the same empty page.
{"data": [], "next_cursor": "WzI2MDI3OSwiNDk3YTg3…", "snapshot": {"built_at": "…", "lag_days": 0.0}}
How fresh is fresh?
indexed_at − published_at is the delay between an article appearing and its statements being available:
Data table
| minutes | statements |
|---|---|
| 0–5 | 6 |
| 5–10 | 38 |
| 10–15 | 30 |
| 15–20 | 12 |
| 20–30 | 14 |
| 30–45 | 0 |
| 45–60 | 0 |
| 60–90 | 0 |
| 90–120 | 0 |
| 120–240 | 0 |
| 240+ | 0 |
Fetching, extraction and clustering take minutes, not seconds. Finance Pulse is for knowing what the market is reading within the quarter hour, not for trading the first second of a headline.
Run this yourself: get a free API key on RapidAPI ↗. The free plan is enough for every example in this guide.